// Heightmap.cpp: implementation of the CHeightmap class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include ".\Terrain\Heightmap.h"
#include "Noise.h"
#include "Layer.h"
#include "CryEditDoc.h"
#include "TerrainFormulaDlg.h"
#include "VegetationMap.h"
#include "TerrainGrid.h"
#include "Util\DynamicArray2D.h"
#include "GameEngine.h"
#include "Console/ConsoleSync.h"
#include "Objects/ShapeObject.h"

#include "Terrain/TerrainManager.h"
#include "SurfaceType.h"

#include <I3DEngine.h>

#include "SegmentedWorld/SegmentedWorldManager.h"

#define DEFAULT_TEXTURE_SIZE 4096

//! Size of terrain sector in units, sector size (in meters) depends from heightmap unit size - it gives more efficient heightmap triangulation
#define SECTOR_SIZE_IN_UNITS 32

//! Default Max Height value.
#define HEIGHTMAP_MAX_HEIGHT 1024

//! Size of noise array.
#define NOISE_ARRAY_SIZE 512
#define NOISE_RANGE 255.0f

//! Filename used when Holding/Fetching heightmap.
#define HEIGHTMAP_HOLD_FETCH_FILE "Heightmap.hld"

#define OVVERIDE_LAYER_SURFACETYPE_FROM 128

//////////////////////////////////////////////////////////////////////////
//! Undo object for heightmap modifications.
class CUndoHeightmapModify : public IUndoObject
{
public:
	CUndoHeightmapModify( int x1,int y1,int width,int height,CHeightmap *heightmap )
	{
		m_hmap.Attach( heightmap->GetData(),heightmap->GetWidth(),heightmap->GetHeight() );
		// Store heightmap block.
		m_rc = CRect( x1,y1,x1+width,y1+height );
		m_rc &= CRect( 0,0,m_hmap.GetWidth(),m_hmap.GetHeight() );
		m_hmap.GetSubImage( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),m_undo );
	}
protected:
	virtual void Release() { delete this; };
	virtual int GetSize() {	return sizeof(*this) + m_undo.GetSize() + m_redo.GetSize(); };
	virtual const char* GetDescription() { return "Heightmap Modify"; };

	virtual void Undo( bool bUndo )
	{
		if (bUndo)
		{
			// Store for redo.
			m_hmap.GetSubImage( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),m_redo );
		}
		// Restore image.
		m_hmap.SetSubImage( m_rc.left,m_rc.top,m_undo );

		int w = m_rc.Width();
		if(w<m_rc.Height()) w =m_rc.Height();

		GetIEditor()->GetHeightmap()->UpdateEngineTerrain( m_rc.left,m_rc.top,w,w,true,false );
		//GetIEditor()->GetHeightmap()->UpdateEngineTerrain( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),true,false );

		if (bUndo)
		{
			AABB box;
			box.min = GetIEditor()->GetHeightmap()->HmapToWorld( CPoint(m_rc.left,m_rc.top) );
			box.max = GetIEditor()->GetHeightmap()->HmapToWorld( CPoint(m_rc.left+w,m_rc.top+w) );
			box.min.z -= 10000;
			box.max.z += 10000;
			GetIEditor()->UpdateViews(eUpdateHeightmap,&box);
		}

		GetIEditor()->GetConsoleSync()->FullSync();

		AABB swBox;
		CSWManager::CoordToAABB(swBox, m_rc.left, m_rc.top, m_rc.right, m_rc.bottom, true, false);
		SW_ON_HMAP_MOD(swBox);
	}
	virtual void Redo()
	{
		if (m_redo.IsValid())
		{
			// Restore image.
			m_hmap.SetSubImage( m_rc.left,m_rc.top,m_redo );
			GetIEditor()->GetHeightmap()->UpdateEngineTerrain( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),true,false );
			
			{
				AABB box;
				box.min = GetIEditor()->GetHeightmap()->HmapToWorld( CPoint(m_rc.left,m_rc.top) );
				box.max = GetIEditor()->GetHeightmap()->HmapToWorld( CPoint(m_rc.left+m_rc.Width(),m_rc.top+m_rc.Height()) );
				box.min.z -= 10000;
				box.max.z += 10000;
				GetIEditor()->UpdateViews(eUpdateHeightmap,&box);
			}
			AABB swBox;
			CSWManager::CoordToAABB(swBox, m_rc.left, m_rc.top, m_rc.right, m_rc.bottom, true, false);
			SW_ON_HMAP_MOD(swBox);
		}

		GetIEditor()->GetConsoleSync()->FullSync();
	}

private:
	CRect m_rc;
	TImage<float> m_undo;
	TImage<float> m_redo;

	TImage<float> m_hmap;		// memory data is shared
};

//////////////////////////////////////////////////////////////////////////
//! Undo object for heightmap modifications.
class CUndoHeightmapInfo : public IUndoObject
{
public:
	CUndoHeightmapInfo( int x1,int y1,int width,int height,CHeightmap *heightmap )
	{
		m_hmap.Attach( heightmap->m_LayerIdBitmap.GetData(),heightmap->GetWidth(),heightmap->GetHeight() );
		// Store heightmap block.
		m_hmap.GetSubImage( x1,y1,width,height,m_undo );
		m_rc = CRect(x1,y1,x1+width,y1+height);
	}
protected:
	virtual void Release() { delete this; };
	virtual int GetSize()	{	return sizeof(*this) + m_undo.GetSize() + m_redo.GetSize(); };
	virtual const char* GetDescription() { return "Heightmap Hole"; };

	virtual void Undo( bool bUndo )
	{
		if (bUndo)
		{
			// Store for redo.
			m_hmap.GetSubImage( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),m_redo );
		}
		// Restore image.
		m_hmap.SetSubImage( m_rc.left,m_rc.top,m_undo );
		GetIEditor()->GetHeightmap()->UpdateEngineHole( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height() );
		SW_ON_HMAP_PAINT(m_rc.left, m_rc.top, m_rc.Width(), m_rc.Height(), true);
	}
	virtual void Redo()
	{
		if (m_redo.IsValid())
		{
			// Restore image.
			m_hmap.SetSubImage( m_rc.left,m_rc.top,m_redo );
			GetIEditor()->GetHeightmap()->UpdateEngineHole( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height() );
			SW_ON_HMAP_PAINT(m_rc.left, m_rc.top, m_rc.Width(), m_rc.Height(), true);
		}
	}

private:
	CRect m_rc;
	TImage<unsigned char> m_undo;
	TImage<unsigned char> m_redo;
	TImage<unsigned char> m_hmap;
};

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CHeightmap::CHeightmap():
m_TerrainRGBTexture("TerrainTexture.pak")
{
	m_fMaxHeight = HEIGHTMAP_MAX_HEIGHT;
	m_bUpdateLayerIdBitmap=false;

	m_pNoise = NULL;
	// Init member variables
	m_pHeightmap = NULL;

	m_iWidth = 0;
	m_iHeight = 0;
	m_pNoise = NULL;
	m_fWaterLevel = 16;
	m_unitSize = 2;
//	m_cachedResolution = 0;
	m_numSectors = 0;
	m_updateModSectors = false;

	m_vegetationMap = new CVegetationMap;

	m_textureSize = DEFAULT_TEXTURE_SIZE;

	m_terrainGrid = new CTerrainGrid(this);

	InitNoise();

	//////////////////////////////////////////////////////////////////////////
	// Load water texture.
	m_waterImage.Allocate(128,128);

	// Retrieve the bits from the bitmap
	CBitmap bmpLoad;
	VERIFY( bmpLoad.Attach(::LoadBitmap(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDB_WATER))) );
	VERIFY( bmpLoad.GetBitmapBits(128 * 128 * sizeof(DWORD), m_waterImage.GetData()) );	

	// check for dx11 to fix water color in terrain preview window
	if (!stricmp(gEnv->pConsole->GetCVar("r_driver")->GetString(), "DX11")) m_waterImage.SwapRedAndBlue();
	//////////////////////////////////////////////////////////////////////////

  GetIEditor()->Get3DEngine()->SetGetLayerIdAtCallback(this);
}

CHeightmap::~CHeightmap()
{
	// Reset the heightmap
	CleanUp();

	delete m_terrainGrid;

	// Remove the noise array
	if (m_pNoise)
	{
		delete m_pNoise;
		m_pNoise = NULL;
	}
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::CleanUp()
{
	////////////////////////////////////////////////////////////////////////
	// Free the data
	////////////////////////////////////////////////////////////////////////

	if (m_pHeightmap)
	{
		delete [] m_pHeightmap;
		m_pHeightmap = NULL;
	}

	m_TerrainRGBTexture.FreeData();

	SAFE_DELETE(m_vegetationMap);

	m_iWidth = 0;
	m_iHeight = 0;
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::Resize( int iWidth, int iHeight,int unitSize,bool bCleanOld )
{
	////////////////////////////////////////////////////////////////////////
	// Resize the heightmap
	////////////////////////////////////////////////////////////////////////

	ASSERT(iWidth && iHeight);

	int prevWidth,prevHeight,prevUnitSize;
	prevWidth = m_iWidth;
	prevHeight = m_iHeight;
	prevUnitSize = m_unitSize;

	t_hmap * prevHeightmap = 0;
	TImage<unsigned char> prevLayerIdBitmap;

	if (bCleanOld)
	{
		// Free old heightmap
		CleanUp();
	}
	else
	{
		if (m_pHeightmap)
		{
			// Remember old state.
			//prevHeightmap.Allocate( m_iWidth,m_iHeight );
			//memcpy( prevHeightmap.GetData(),m_pHeightmap,prevHeightmap.GetSize() );
			prevHeightmap = m_pHeightmap;
			m_pHeightmap = 0;
		}
		if (m_LayerIdBitmap.IsValid())
		{
			prevLayerIdBitmap.Allocate( m_iWidth,m_iHeight );
			memcpy( prevLayerIdBitmap.GetData(),m_LayerIdBitmap.GetData(),prevLayerIdBitmap.GetSize() );
		}
	}

	// Save width and height
	m_iWidth = iWidth;
	m_iHeight = iHeight;
	m_unitSize = unitSize;

	int sectorSize = m_unitSize*SECTOR_SIZE_IN_UNITS;
	m_numSectors = (m_iWidth*m_unitSize) / sectorSize;

	bool			boChangedTerrainDimensions(prevWidth != m_iWidth || prevHeight != m_iHeight || prevUnitSize != m_unitSize);
	ITerrain *pTerrain = GetIEditor()->Get3DEngine()->GetITerrain();

	if (!CSWManager::Get().IsSWDoc())
	if(CTerrainManager::GetTerrainManager().IsNewTerranTextureSystem())
	{
		uint32 dwTerrainSizeInMeters = m_iWidth*unitSize;
		uint32 dwTerrainTextureResolution = dwTerrainSizeInMeters/2;			// terrain texture resolution scaled with heightmap - bad: hard coded factor 2m=1texel
    dwTerrainTextureResolution = min(dwTerrainTextureResolution,(uint32)16384);

		uint32 dwTileResolution = 512;			// to avoid having too many tiles we try to get big ones

		// As dwTileCount must be at least 1, dwTerrainTextureResolution must be at most equals to 
		// dwTerrainTextureResolution.
		if(dwTerrainTextureResolution<=dwTileResolution)
			dwTileResolution=dwTerrainTextureResolution;

		uint32 dwTileCount = dwTerrainTextureResolution/dwTileResolution;

		m_TerrainRGBTexture.AllocateTiles(dwTileCount,dwTileCount,dwTileResolution);
	}

	// Allocate new data
	if (m_pHeightmap)
		delete [] m_pHeightmap;
	m_pHeightmap = new t_hmap[iWidth * iHeight];

	CryLog("allocating editor height map (%dx%d)*4",iWidth,iHeight);

	Verify();

	m_LayerIdBitmap.Allocate( iWidth,iHeight );

	if(	prevWidth < m_iWidth || prevHeight < m_iHeight || prevUnitSize < m_unitSize)
		Clear();

	if (bCleanOld)
	{
		// Set to zero
		Clear();
	}
	else
	{
		// Copy from previous data.
		if (prevHeightmap && prevLayerIdBitmap.IsValid())
		{
			CWaitCursor wait;
			if(prevUnitSize == m_unitSize)
				CopyFrom( prevHeightmap,prevLayerIdBitmap.GetData(),prevWidth );
			else
				CopyFromInterpolate( prevHeightmap,prevLayerIdBitmap.GetData(),prevWidth,  float(prevUnitSize) / m_unitSize);
		}
	}

	if(prevHeightmap)
		delete [] prevHeightmap;

	m_terrainGrid->InitSectorGrid( m_numSectors );
	m_terrainGrid->SetResolution( m_textureSize );

	// This must run only when creating a new terrain.
	if (
			(!boChangedTerrainDimensions)||
			(!pTerrain)
		)
	{
		if (bCleanOld)
		{
			m_vegetationMap = new CVegetationMap;
			m_vegetationMap->Allocate( this );
		}
		else
		{
			CWaitCursor wait;
			bool bVegSaved = false;
			CXmlArchive ar("Temp");
			if (m_vegetationMap)
			{
				m_vegetationMap->Serialize( ar );
				bVegSaved = true;
			}

			if (m_vegetationMap)
				delete m_vegetationMap;

			m_vegetationMap = new CVegetationMap;
			m_vegetationMap->Allocate( this );

			if (bVegSaved)
			{
				// Load back from archive.
				ar.bLoading = true;
				m_vegetationMap->Serialize(ar);
			}
		}
	}

	if (!bCleanOld)
	{
		int numLayers = CTerrainManager::GetTerrainManager().GetLayerCount();
		for (int i = 0; i < numLayers; i++)
		{
			CLayer *pLayer = CTerrainManager::GetTerrainManager().GetLayer(i);
			pLayer->AllocateMaskGrid();
		}
	}

	if(boChangedTerrainDimensions)
	{
		if (pTerrain)
		{
			static bool bNoReentrant = false;
			if (!bNoReentrant)
			{
				bNoReentrant = true;
				// This will reload the level with the new terrain size.
				GetIEditor()->GetDocument()->HoldToFile( "TemporaryEditorResize.cry" );
				GetIEditor()->GetDocument()->FetchFromFile( "TemporaryEditorResize.cry" );
				bNoReentrant = false;
			}

			/*
			XmlNodeRef	xmlObjectsNode;

			//////////////////////////////////////////////////////////////////////////
			CXmlArchive ar("Temp");
			//////////////////////////////////////////////////////////////////////////

			// We always clear the selection.
			GetIEditor()->ClearSelection();

			// And we don't really want to have any tool activated.
			GetIEditor()->SetEditTool(NULL);

			// As ALL engine objects are deleted when we delete the terrain...
			//... we delete all objects in the editor as well.

			if (!bCleanOld)
			{
				// Here we create the placeholder to store object information
				// between the resizing operation.
				xmlObjectsNode=gEnv->pSystem->CreateXmlNode("Root");

				// Here we are saving all objects to our temporary buffer so we can 
				// restore then after they are destroyed by the calls of DeleteAllObjects
				// and DeleteTerrain.
				GetIEditor()->GetObjectManager()->Serialize(xmlObjectsNode,false,SERIALIZE_ALL);

				//////////////////////////////////////////////////////////////////////////
				// Here we save the current vegetation for later use.
				if (m_vegetationMap)
				{
					m_vegetationMap->Serialize( ar );
				}			
				//////////////////////////////////////////////////////////////////////////
			}

			//////////////////////////////////////////////////////////////////////////
			// And we clear all the current vegetation.
			SAFE_DELETE(m_vegetationMap);
			m_vegetationMap = new CVegetationMap;
			m_vegetationMap->Allocate( this );
			//////////////////////////////////////////////////////////////////////////

			// MUST be called before DeleteTerrain, as DeleteTerrain will
			// delete all engine objects, we can't afford to have references
			// to them in the object manager.
			GetIEditor()->GetObjectManager()->DeleteAllObjects();

			// Reset all resources in game.
			GetIEditor()->GetGameEngine()->ResetResources();

			GetIEditor()->Notify( eNotify_OnBeginTerrainRebuild );


			GetIEditor()->GetGameEngine()->ReloadLevel();

			GetIEditor()->Notify( eNotify_OnEndTerrainRebuild );

			if (!bCleanOld)
			{
				//////////////////////////////////////////////////////////////////////////
				// And finally we load the vegetation back.
				ar.bLoading = true;
				m_vegetationMap->Serialize(ar);
				//////////////////////////////////////////////////////////////////////////

				// Now that we have a new terrain, we can add its objects again.
				GetIEditor()->GetObjectManager()->Serialize(xmlObjectsNode,true,SERIALIZE_ALL);
			}
			*/
		}
	}

	// We modified the heightmap.
	SetModified();
}


//////////////////////////////////////////////////////////////////////////
void CHeightmap::PaintLayerId( const float fpx, const float fpy, const SEditorPaintBrush &brush, const uint32 dwLayerId )
{
	assert(dwLayerId<=(0xff>>HEIGHTMAP_INFO_SFTYPE_SHIFT));
	uint8 ucLayerInfoData = (dwLayerId<<HEIGHTMAP_INFO_SFTYPE_SHIFT)&HEIGHTMAP_INFO_SFTYPE_MASK;

	SEditorPaintBrush cpyBrush=brush;

	cpyBrush.bBlended=false;
	cpyBrush.color=ucLayerInfoData;

	CImagePainter painter;

	painter.PaintBrush(fpx,fpy,m_LayerIdBitmap,cpyBrush);

  if(IVoxTerrain * pVox = GetIEditor()->Get3DEngine()->GetIVoxTerrain())
    pVox->PaintLayerId(fpx, fpy, brush.fRadius, brush.hardness, dwLayerId);

#ifdef SEG_WORLD
	float fTerrainSize = (float)GetISystem()->GetI3DEngine()->GetTerrainSize();
	int x = (int)floor(fpx*fTerrainSize - brush.fRadius*fTerrainSize);
	int y = (int)floor(fpy*fTerrainSize - brush.fRadius*fTerrainSize);
	int w = (int)ceil (brush.fRadius*fTerrainSize * 2);
	int h = (int)ceil (brush.fRadius*fTerrainSize * 2);
	
	SW_ON_HMAP_PAINT(x, y, w, h, true);
#endif
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::MarkUsedLayerIds( bool bFree[256] ) const
{
	for(uint32 dwY=0;dwY<m_iHeight;++dwY)
	for(uint32 dwX=0;dwX<m_iWidth;++dwX)
	{
		uint32 ucId = GetLayerIdAt(dwX,dwY);

		assert(ucId<=(0xff>>HEIGHTMAP_INFO_SFTYPE_SHIFT));

		bFree[ucId]=false;
	}
}


//////////////////////////////////////////////////////////////////////////
CPoint CHeightmap::WorldToHmap( const Vec3 &wp )
{
	//swap x/y.
	return CPoint(wp.y/m_unitSize,wp.x/m_unitSize);
}
	
//////////////////////////////////////////////////////////////////////////
Vec3 CHeightmap::HmapToWorld( CPoint hpos )
{
	return Vec3(hpos.y*m_unitSize,hpos.x*m_unitSize,0);
}

//////////////////////////////////////////////////////////////////////////
CRect CHeightmap::WorldBoundsToRect( const AABB &worldBounds )
{
	CPoint p1 = WorldToHmap(worldBounds.min);
	CPoint p2 = WorldToHmap(worldBounds.max);
	if (p1.x > p2.x)
		std::swap(p1.x,p2.x);
	if (p1.y > p2.y)
		std::swap(p1.y,p2.y);
	CRect rc;
	rc.SetRect(p1,p2);
	rc.IntersectRect(rc,CRect(0,0,m_iWidth,m_iHeight));
	return rc;
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::InvalidateLayers()
{
	CTerrainManager::GetTerrainManager().InvalidateLayers();
}

void CHeightmap::Clear()
{
	if (m_iWidth && m_iHeight)
	{
		memset( m_pHeightmap,0,sizeof(t_hmap)*m_iWidth*m_iHeight );
		m_LayerIdBitmap.Clear();
	}
};

void CHeightmap::SetMaxHeight( float fMaxHeight )
{
	m_fMaxHeight = fMaxHeight;

	// Clamp heightmap to the max height.
	int nSize = GetWidth()*GetHeight();
	for (int i = 0; i < nSize; i++)
	{
		if (m_pHeightmap[i] > m_fMaxHeight)
			m_pHeightmap[i] = m_fMaxHeight;
	}
}

void CHeightmap::LoadBMP(LPCSTR pszFileName, bool bNoiseAndFilter)
{
	////////////////////////////////////////////////////////////////////////
	// Load a BMP file as current heightmap
	////////////////////////////////////////////////////////////////////////
	CImage image;
	CImage tmpImage;
	CImage hmap;

	if (!CImageUtil::LoadImage( pszFileName,tmpImage ))
	{
		AfxMessageBox( _T("Load image failed."),MB_OK|MB_ICONERROR );
		return;
	}

	image.RotateOrt(tmpImage, 0);

	if (image.GetWidth() != m_iWidth || image.GetHeight() != m_iHeight)
	{
		hmap.Allocate( m_iWidth,m_iHeight );
		CImageUtil::ScaleToFit( image,hmap );
	}
	else
	{
		hmap.Attach( image );
	}

	float fInvPrecisionScale = 1.0f / GetBytePrecisionScale();

	uint32 *pData = hmap.GetData();
	int size = hmap.GetWidth()*hmap.GetHeight();
	for (int i = 0; i < size; i++)
	{
		// Extract a color channel and rescale the value
		// before putting it into the heightmap
		int col = pData[i] & 0x000000FF;
		m_pHeightmap[i] = (float)col * fInvPrecisionScale;
	}
	/*
	// Noise and filter ?
	if (bNoiseAndFilter)
	{
		Smooth();
		Noise();
	}
	*/

	// We modified the heightmap.
	SetModified();

	CLogFile::FormatLine("Heightmap loaded from file %s", pszFileName);
}

void CHeightmap::SaveImage(LPCSTR pszFileName, bool bNoiseAndFilter)
{
	uint32 *pImageData = NULL;
	unsigned int i, j;
	uint8 iColor;
	t_hmap *pHeightmap = NULL;
	UINT iWidth = GetWidth();
	UINT iHeight = GetHeight();

	// Allocate memory to export the heightmap
	CImage image;
	image.Allocate(iWidth,iHeight);
	pImageData = image.GetData();

	// Get a pointer to the heightmap data
	pHeightmap = GetData();

	float fPrecisionScale = GetBytePrecisionScale();

	// BMP
	// Write heightmap into the image data array
	for (j=0; j<iHeight; j++)
	{
		for (i=0; i<iWidth; i++)
		{
			// Get a normalized grayscale value from the heigthmap
			iColor = (uint8)__min((pHeightmap[i + j * iWidth]*fPrecisionScale), 255.0f);
					
			// Create a BGR grayscale value and store it in the image
			// data array
			pImageData[i + j * iWidth] = (iColor << 16) | (iColor << 8) | iColor;
		}
	}
	
	CImage newImage;
	newImage.RotateOrt(image, 0);

	// Save the heightmap into the bitmap	
	CImageUtil::SaveImage( pszFileName, newImage );
}

void CHeightmap::SavePGM( const CString &pgmFile )
{
	CImage image;
	image.Allocate(m_iWidth,m_iHeight);

	float fPrecisionScale = GetShortPrecisionScale();
	for (int j=0; j<m_iHeight; j++)
	{
		for (int i=0; i<m_iWidth; i++)
		{
			unsigned int h = ftoi( GetXY(i,j)*fPrecisionScale+0.5f );
			if (h > 0xFFFF) h = 0xFFFF;
			image.ValueAt(i,j) = ftoi( h );
		}
	}

	CImage newImage;
	newImage.RotateOrt(image, 0);
	CImageUtil::SaveImage( pgmFile,newImage );
}

void CHeightmap::LoadPGM( const CString &pgmFile, float fScaleLow, float fScaleHigh )
{
	CImage image;
	CImage tmpImage;
	CImageUtil::LoadImage( pgmFile,tmpImage );

	image.RotateOrt(tmpImage, 0);

	if (image.GetWidth() != m_iWidth || image.GetHeight() != m_iHeight)
	{
		MessageBox( NULL,"PGM dimensions do not match dimensions of heighmap","Warning",MB_OK|MB_ICONEXCLAMATION );
		return;
	}

	float fPrecisionScale = GetShortPrecisionScale();

	if (fScaleLow >= 0.0f && fScaleHigh > 0.0f) 
  {
		float fMultiplier = (fScaleHigh - fScaleLow) / 0xFFFF;

	for (int j=0; j<m_iHeight; j++)
	{
		for (int i=0; i<m_iWidth; i++)
				GetXY(i,j) = fScaleLow + image.ValueAt(i,j) * fMultiplier;
    }
	} 
  else 
		{
		for (int j=0; j<m_iHeight; j++)
		{
			for (int i=0; i<m_iWidth; i++)
			GetXY(i,j) = image.ValueAt(i,j) / fPrecisionScale;
		}
	}
}

//! Save heightmap in RAW format.
void CHeightmap::SaveRAW(  const CString &rawFile )
{
	CString str;
	FILE *file = gEnv->pCryPak->FOpen( rawFile,"wb" );
	if (!file)
	{
		str.Format( "Error saving file %s",(const char*)rawFile );
		MessageBox( NULL,str,"Warning",MB_OK|MB_ICONEXCLAMATION );
		return;
	}

	CWordImage image;
	image.Allocate( m_iWidth,m_iHeight );

	float fPrecisionScale = GetShortPrecisionScale();

	for (int j=0; j<m_iHeight; j++)
	{
		for (int i=0; i<m_iWidth; i++)
		{
			unsigned int h = ftoi( GetXY(i,j)*fPrecisionScale+0.5f );
			if (h > 0xFFFF) h = 0xFFFF;
			image.ValueAt(i,j) = h;
		}
	}

	CWordImage newImage;
	newImage.RotateOrt(image, 0);

	fwrite( newImage.GetData(),newImage.GetSize(),1,file );

	fclose(file);
}

//! Load heightmap from RAW format.
void	CHeightmap::LoadRAW(  const CString &rawFile, float fScaleLow, float fScaleHigh )
{
	CString str;
	FILE *file = gEnv->pCryPak->FOpen( rawFile,"rb" );
	if (!file)
	{
		str.Format( "Error loading file %s",(const char*)rawFile );
		MessageBox( NULL,str,"Warning",MB_OK|MB_ICONEXCLAMATION );
		return;
	}
	fseek( file,0,SEEK_END );
	int fileSize = ftell( file );
	fseek( file,0,SEEK_SET );

	if (fileSize != m_iWidth*m_iHeight*2)
	{
		str.Format( "Bad RAW file, RAW file must be %d x %d 16bit image",m_iWidth,m_iHeight );
		MessageBox( NULL,str,"Warning",MB_OK|MB_ICONEXCLAMATION );
		fclose(file);
		return;
	}
	CWordImage image;
	CWordImage tmpImage;
	tmpImage.Allocate( m_iWidth,m_iHeight );
	fread( tmpImage.GetData(),tmpImage.GetSize(),1,file );
	fclose(file);

	image.RotateOrt(tmpImage, 0);

	float fPrecisionScale = GetShortPrecisionScale();

	if (fScaleLow >= 0.0f && fScaleHigh > 0.0f) 
  {
		float fMultiplier = (fScaleHigh - fScaleLow) / 0xFFFF;

	for (int j=0; j<m_iHeight; j++)
	{
		for (int i=0; i<m_iWidth; i++)
				GetXY(i,j) = fScaleLow + image.ValueAt(i,j) * fMultiplier;
    }
	} 
  else 
		{
		for (int j=0; j<m_iHeight; j++)
		{
			for (int i=0; i<m_iWidth; i++)
				GetXY(i,j) = image.ValueAt(i,j) / fPrecisionScale;
		}
	}
}

void CHeightmap::Noise()
{
	////////////////////////////////////////////////////////////////////////
	// Add noise to the heightmap
	////////////////////////////////////////////////////////////////////////
	InitNoise();
	assert(m_pNoise);

	Verify();

	const float fInfluence = 10.0f;

	// Calculate the way we have to swap the noise. We do this to avoid
	// singularities when a noise array is aplied more than once
	srand(clock());
	UINT iNoiseSwapMode = rand() % 5;

	// Calculate a noise offset for the same reasons
	UINT iNoiseOffsetX = rand() % NOISE_ARRAY_SIZE;
	UINT iNoiseOffsetY = rand() % NOISE_ARRAY_SIZE;

	UINT iNoiseX, iNoiseY;

	for (uint64 j=1; j<m_iHeight - 1; j++)
	{
		// Precalculate for better speed
		uint64 iCurPos = j * m_iWidth  + 1;

		for (uint64 i=1; i<m_iWidth - 1; i++)
		{
			// Next pixel
			iCurPos++;

			// Skip amnything below the water level
			if (m_pHeightmap[iCurPos] > 0.0f && m_pHeightmap[iCurPos] >= m_fWaterLevel)
			{
				// Swap the noise
				switch (iNoiseSwapMode)
				{
				case 0:
					iNoiseX = i;
					iNoiseY = j;
					break;
				case 1:
					iNoiseX = j;
					iNoiseY = i;
					break;
				case 2:
					iNoiseX = m_iWidth - i;
					iNoiseY = j;
					break;
				case 3:
					iNoiseX = i;
					iNoiseY = m_iHeight - j;
					break;
				case 4:
					iNoiseX = m_iWidth - i;
					iNoiseY = m_iHeight - j;
					break;
				}

				// Add the random noise offset
				iNoiseX += iNoiseOffsetX;
				iNoiseY += iNoiseOffsetY;

				float fInfluenceValue = m_pNoise->m_Array[iNoiseX % NOISE_ARRAY_SIZE][iNoiseY % NOISE_ARRAY_SIZE] / NOISE_RANGE * fInfluence - fInfluence/2;

				// Add the signed noise
				m_pHeightmap[iCurPos] = __min(m_fMaxHeight, __max(m_fWaterLevel, m_pHeightmap[iCurPos] + fInfluenceValue));
			}
		}
	}

	// We modified the heightmap.
	SetModified();
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::Smooth( CFloatImage &hmap,const CRect &rect )
{
	int w = hmap.GetWidth();
	int h = hmap.GetHeight();

	int x1 = max((int)rect.left+2,1);
	int y1 = max((int)rect.top+2,1);
	int x2 = min((int)rect.right-2,w-1);
	int y2 = min((int)rect.bottom-2,h-1);

	t_hmap* pData = hmap.GetData();

	int i,j,pos;
	// Smooth it
	for (j=y1; j < y2; j++)
	{
		pos = j*w;
		for (i=x1; i < x2; i++)
		{
			pData[i + pos] = 
				(pData[i + pos] +
				pData[i + 1 + pos] +
				pData[i - 1 + pos] +
				pData[i + pos+w] +
				pData[i + pos-w] + 
				pData[(i - 1) + pos-w] +
				pData[(i + 1) + pos-w] +
				pData[(i - 1) + pos+w] +
				pData[(i + 1) + pos+w])
				* (1.0f/9.0f);
		}
	}
	/*
	for (j=y2-1; j > y1; j--)
	{
		pos = j*w;
		for (i=x2-1; i > x1; i--)
		{
			pData[i + pos] = 
				(pData[i + pos] +
				pData[i + 1 + pos] +
				pData[i - 1 + pos] +
				pData[i + pos+w] +
				pData[i + pos-w] + 
				pData[(i - 1) + pos-w] +
				pData[(i + 1) + pos-w] +
				pData[(i - 1) + pos+w] +
				pData[(i + 1) + pos+w]) 
				* (1.0f/9.0f);
		}
	}
	*/
}

void CHeightmap::Smooth()
{
	////////////////////////////////////////////////////////////////////////
	// Smooth the heightmap
	////////////////////////////////////////////////////////////////////////

	unsigned int i, j;

	Verify();

	// Smooth it
	for (i=1; i<m_iWidth-1; i++)
		for (j=1; j<m_iHeight-1; j++)
		{
			m_pHeightmap[i + j * m_iWidth] = 
				(m_pHeightmap[i + j * m_iWidth] +
				 m_pHeightmap[(i + 1) + j * m_iWidth] +
				 m_pHeightmap[i + (j + 1) * m_iWidth] +
				 m_pHeightmap[(i + 1) + (j + 1) * m_iWidth] + 
				 m_pHeightmap[(i - 1) + j * m_iWidth] +
				 m_pHeightmap[i + (j - 1) * m_iWidth] + 
				 m_pHeightmap[(i - 1) + (j - 1) * m_iWidth] +
				 m_pHeightmap[(i + 1) + (j - 1) * m_iWidth] +
				 m_pHeightmap[(i - 1) + (j + 1) * m_iWidth])
				 / 9.0f;
		}

	// We modified the heightmap.
	SetModified();
}

void CHeightmap::Invert()
{
	////////////////////////////////////////////////////////////////////////
	// Invert the heightmap
	////////////////////////////////////////////////////////////////////////

	unsigned int i;

	Verify();

	for (i=0; i<m_iWidth * m_iHeight; i++)
	{
		m_pHeightmap[i] = m_fMaxHeight - m_pHeightmap[i];
		if (m_pHeightmap[i] > m_fMaxHeight)
			m_pHeightmap[i] = m_fMaxHeight;
		if (m_pHeightmap[i] < 0)
			m_pHeightmap[i] = 0;
	}

	// We modified the heightmap.
	SetModified();
}

void CHeightmap::Normalize()
{
	////////////////////////////////////////////////////////////////////////
	// Normalize the heightmap to a 0 - m_fMaxHeight range
	////////////////////////////////////////////////////////////////////////

	unsigned int i, j;
	float fLowestPoint = 512000.0f, fHighestPoint = -512000.0f;
	float fValueRange;
	float fHeight;

	Verify();

	// Find the value range
	for (i=0; i<m_iWidth; i++)
		for (j=0; j<m_iHeight; j++)
		{
			fLowestPoint = __min(fLowestPoint, GetXY(i, j));
			fHighestPoint = __max(fHighestPoint, GetXY(i, j));
		}

	// Storing the value range in this way saves us a division and a multiplication
	fValueRange = (1.0f / (fHighestPoint - (float) fLowestPoint)) * m_fMaxHeight;

	// Normalize the heightmap
	for (i=0; i<m_iWidth; i++)
		for (j=0; j<m_iHeight; j++)
		{
			fHeight = GetXY(i, j);

//			fHeight += (float) fabs(fLowestPoint);
			fHeight -= fLowestPoint;
			fHeight *= fValueRange;

//			fHeight=128.0f;

			SetXY(i, j, fHeight);
		}

	fLowestPoint = 512000.0f, fHighestPoint = -512000.0f;

	// Find the value range
/*	for (i=0; i<m_iWidth; i++)
		for (j=0; j<m_iHeight; j++)
		{
			fLowestPoint = __min(fLowestPoint, GetXY(i, j));
			fHighestPoint = __max(fHighestPoint, GetXY(i, j));
		}*/

	// We modified the heightmap.
	SetModified();
}

bool CHeightmap::GetDataEx(t_hmap *pData, UINT iDestWidth, bool bSmooth, bool bNoise)
{
	if( !m_pHeightmap )
	{
		return false;
	}
	////////////////////////////////////////////////////////////////////////
	// Retrieve heightmap data. Scaling and noising is optional
	////////////////////////////////////////////////////////////////////////

	unsigned int i, j;
	long iXSrcFl, iXSrcCe, iYSrcFl, iYSrcCe;
	float fXSrc, fYSrc;
	float fHeight[4];
	float fHeightWeight[4];
	float fHeightBottom;
	float fHeightTop;
	UINT dwHeightmapWidth = GetWidth();
	t_hmap *pDataStart = pData;

	Verify();

	bool bProgress = iDestWidth > 1024;
	// Only log significant allocations. This also prevents us from cluttering the
	// log file during the lightmap preview generation
	if (iDestWidth > 1024)
		CLogFile::FormatLine("Retrieving heightmap data (Width: %i)...", iDestWidth);

	CWaitProgress wait( "Scaling Heightmap",bProgress );
	
	// Loop trough each field of the new image and interpolate the value
	// from the source heightmap
	for (j=0; j<iDestWidth; j++)
	{
		if (bProgress)
		{
			if (!wait.Step( j*100/iDestWidth ))
				return false;
		}

		// Calculate the average source array position
		fYSrc = ((float) j / (float) iDestWidth) * dwHeightmapWidth;
		assert(fYSrc >= 0.0f && fYSrc <= dwHeightmapWidth);
		
		// Precalculate floor and ceiling values. Use fast asm integer floor and
		// fast asm float / integer conversion
		iYSrcFl = ifloor(fYSrc);
		iYSrcCe = iYSrcFl+1;

		// Clamp the ceiling coordinates to a save range
		if (iYSrcCe >= (int) dwHeightmapWidth)
			iYSrcCe = dwHeightmapWidth - 1;

		// Distribution between top and bottom height values
		fHeightWeight[3] = fYSrc-(float)iYSrcFl;
		fHeightWeight[2] = 1.0f-fHeightWeight[3];

		for (i=0; i<iDestWidth; i++)
		{
			// Calculate the average source array position
			fXSrc = ((float) i / (float) iDestWidth) * dwHeightmapWidth;
			assert(fXSrc >= 0.0f && fXSrc <= dwHeightmapWidth);

			// Precalculate floor and ceiling values. Use fast asm integer floor and
			// fast asm float / integer conversion
			iXSrcFl = ifloor(fXSrc);
			iXSrcCe = iXSrcFl+1;
			
			// Distribution between left and right height values
			fHeightWeight[1] = fXSrc-(float)iXSrcFl;
			fHeightWeight[0] = 1.0f-fHeightWeight[1];
/*
			// Avoid error when floor() and ceil() return the same value
			if (fHeightWeight[0] == 0.0f && fHeightWeight[1] == 0.0f)
			{
				fHeightWeight[0] = 0.5f;
				fHeightWeight[1] = 0.5f;
			}

			// Calculate how much weight each height value has

			// Avoid error when floor() and ceil() return the same value
			if (fHeightWeight[2] == 0.0f && fHeightWeight[3] == 0.0f)
			{
				fHeightWeight[2] = 0.5f;
				fHeightWeight[3] = 0.5f;
			}
			*/


			if (iXSrcCe >= (int) dwHeightmapWidth)
				iXSrcCe = dwHeightmapWidth - 1;

			// Get the four nearest height values
			fHeight[0] = (float) m_pHeightmap[iXSrcFl + iYSrcFl * dwHeightmapWidth];
			fHeight[1] = (float) m_pHeightmap[iXSrcCe + iYSrcFl * dwHeightmapWidth];
			fHeight[2] = (float) m_pHeightmap[iXSrcFl + iYSrcCe * dwHeightmapWidth];
			fHeight[3] = (float) m_pHeightmap[iXSrcCe + iYSrcCe * dwHeightmapWidth];

			// Interpolate between the four nearest height values
	
			// Get the height for the given X position trough interpolation between
			// the left and the right height
			fHeightBottom = (fHeight[0] * fHeightWeight[0] + fHeight[1] * fHeightWeight[1]);
			fHeightTop    = (fHeight[2] * fHeightWeight[0] + fHeight[3] * fHeightWeight[1]);

			// Set the new value in the destination heightmap
			*pData++ = (t_hmap) (fHeightBottom * fHeightWeight[2] + fHeightTop * fHeightWeight[3]);
		}
	}

	if (bNoise)
	{
		InitNoise();

		pData = pDataStart;
		// Smooth it
		for (i=1; i < iDestWidth-1; i++)
			for (j=1; j <  iDestWidth-1; j++)
			{
				*pData++ += (((float)rand())/RAND_MAX) * 1.0f/16.0f;
			}
	}

	if (bSmooth)
	{
		CFloatImage img;
		img.Attach( pDataStart,iDestWidth,iDestWidth );
		Smooth( img,CRect(0,0,iDestWidth,iDestWidth) );
	}

	// Cache rescaled data.
//	m_cachedResolution = iDestWidth;

	/*
	int nSize = m_cachedResolution*m_cachedResolution*sizeof(float);
	CMemoryBlock temp;
	temp.Allocate( nSize );
	temp.Copy( pDataStart,nSize );
	temp.Compress( m_cachedHeightmap );
	*/
	
	return true;
}



//////////////////////////////////////////////////////////////////////////
bool CHeightmap::GetData( CRect &srcRect, const int resolution, const CPoint vTexOffset, CFloatImage &hmap, bool bSmooth,bool bNoise )
{
	////////////////////////////////////////////////////////////////////////
	// Retrieve heightmap data. Scaling and noising is optional
	////////////////////////////////////////////////////////////////////////

	unsigned int i, j;
	int iXSrcFl, iXSrcCe, iYSrcFl, iYSrcCe;
	float fXSrc, fYSrc;
	float fHeight[4];
	float fHeightWeight[4];
	float fHeightBottom;
	float fHeightTop;
	UINT dwHeightmapWidth = GetWidth();
	
	int width = hmap.GetWidth();

	// clip within source
	int x1 = max((int)srcRect.left,0);
	int y1 = max((int)srcRect.top,0);
	int x2 = min((int)srcRect.right,resolution);
	int y2 = min((int)srcRect.bottom,resolution);

	// clip within dest hmap
	x1 = max(x1,(int)vTexOffset.x);
	y1 = max(y1,(int)vTexOffset.y);
	x2 = min(x2,hmap.GetWidth()+(int)vTexOffset.x);
	y2 = min(y2,hmap.GetHeight()+(int)vTexOffset.y);

	float fScaleX = m_iWidth/(float)hmap.GetWidth();
	float fScaleY = m_iHeight/(float)hmap.GetHeight();

	t_hmap *pDataStart = hmap.GetData();

	int trgW = x2 - x1;

	bool bProgress = trgW > 1024;
	CWaitProgress wait( "Scaling Heightmap",bProgress );
	
	// Loop trough each field of the new image and interpolate the value
	// from the source heightmap
	for (j = y1; j < y2; j++)
	{
		if (bProgress)
		{
			if (!wait.Step( (j-y1)*100/(y2-y1) ))
				return false;
		}

		t_hmap *pData = &pDataStart[(j-vTexOffset.y)*width + x1-vTexOffset.x];

		// Calculate the average source array position
		fYSrc = j*fScaleY;
		assert(fYSrc >= 0.0f && fYSrc <= dwHeightmapWidth);
		
		// Precalculate floor and ceiling values. Use fast asm integer floor and
		// fast asm float / integer conversion
		iYSrcFl = ifloor(fYSrc);
		iYSrcCe = iYSrcFl+1;

		// Clamp the ceiling coordinates to a save range
		if (iYSrcCe >= (int) dwHeightmapWidth)
			iYSrcCe = dwHeightmapWidth - 1;


		// Distribution between top and bottom height values
		fHeightWeight[3] = fYSrc - (float)iYSrcFl;
		fHeightWeight[2] =1.0f - fHeightWeight[3];

		for (i = x1; i < x2; i++)
		{
			// Calculate the average source array position
			fXSrc = i * fScaleX;
			assert(fXSrc >= 0.0f && fXSrc <= dwHeightmapWidth);

			// Precalculate floor and ceiling values. Use fast asm integer floor and
			// fast asm float / integer conversion
			iXSrcFl = ifloor(fXSrc);
			iXSrcCe = iXSrcFl+1;
			
			if (iXSrcCe >= (int) dwHeightmapWidth)
				iXSrcCe = dwHeightmapWidth - 1;

			// Distribution between left and right height values
			fHeightWeight[1] = fXSrc - (float)iXSrcFl;
			fHeightWeight[0] = 1.0f - fHeightWeight[1];

			// Get the four nearest height values
			fHeight[0] = (float) m_pHeightmap[iXSrcFl + iYSrcFl * dwHeightmapWidth];
			fHeight[1] = (float) m_pHeightmap[iXSrcCe + iYSrcFl * dwHeightmapWidth];
			fHeight[2] = (float) m_pHeightmap[iXSrcFl + iYSrcCe * dwHeightmapWidth];
			fHeight[3] = (float) m_pHeightmap[iXSrcCe + iYSrcCe * dwHeightmapWidth];

			// Interpolate between the four nearest height values
	
			// Get the height for the given X position trough interpolation between
			// the left and the right height
			fHeightBottom = (fHeight[0] * fHeightWeight[0] + fHeight[1] * fHeightWeight[1]);
			fHeightTop    = (fHeight[2] * fHeightWeight[0] + fHeight[3] * fHeightWeight[1]);

			// Set the new value in the destination heightmap
			*pData++ = (t_hmap) (fHeightBottom * fHeightWeight[2] + fHeightTop * fHeightWeight[3]);
		}
	}


	// Only if requested resolution, higher then current resolution.
	if(resolution > m_iWidth)
	if(bNoise)
	{
		InitNoise();

		int ye=hmap.GetHeight();
		int xe=hmap.GetWidth();

		t_hmap *pData = pDataStart;

		// add noise
		for (int y = 0; y < ye; y++)
		{
			for (int x = 0; x < xe; x++)
			{
				*pData++ += (((float)rand())/RAND_MAX) * 1.0f/16.0f;
				//*pData++ += (((float)rand())/RAND_MAX) * 1.0f/8.0f;
			}
		}
	}

	if(bSmooth)
		Smooth( hmap,srcRect-vTexOffset );

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool CHeightmap::GetPreviewBitmap( DWORD *pBitmapData,int width,bool bSmooth, bool bNoise,CRect *pUpdateRect,bool bShowWater )
{
	bool res = false;
	CFloatImage hmap;

	hmap.Allocate(width,width);
	t_hmap *pHeightmap = hmap.GetData();

	CRect destUpdateRect = CRect(0,0,width,width);

	if (pUpdateRect)
	{
		CRect destUpdateRect = *pUpdateRect;
		float fScale = (float)width / m_iWidth;
		destUpdateRect.left *= fScale;
		destUpdateRect.right *= fScale;
		destUpdateRect.top *= fScale;
		destUpdateRect.bottom *= fScale;

		res = GetData( destUpdateRect,width,CPoint(0,0),hmap,bSmooth,bNoise );
	}
	else
	{
		res = GetDataEx( pHeightmap,width,bSmooth,bNoise );
	}

	float fByteScale = GetBytePrecisionScale();
	if (res)
	{
		t_hmap *pH = pHeightmap;

		uint32 *pWaterTexData = m_waterImage.GetData();

		CRect bounds(0,0,width,width);
		if (pUpdateRect)
		{
			CRect destUpdateRect = *pUpdateRect;
			float fScale = (float)width / m_iWidth;
			destUpdateRect.left *= fScale;
			destUpdateRect.right *= fScale;
			destUpdateRect.top *= fScale;
			destUpdateRect.bottom *= fScale;

			bounds.IntersectRect(bounds,destUpdateRect);
		}

		if (bShowWater)
		{
			int32 nWaterLevel = ftoi(m_fWaterLevel * fByteScale);
			// Fill the preview with the heightmap image
			for (int iY=bounds.top; iY < bounds.bottom; iY++)
				for (int iX=bounds.left; iX < bounds.right; iX++)
				{
					// Use the height value as grayscale if the current heightmap point is above the water
					// level. Use a texel from the tiled water texture when it is below the water level
					int32 val = ftoi(pH[iX + iY*width] * fByteScale);
					pBitmapData[iX + iY*width] = (val >= nWaterLevel) ? RGB(val,val,val) : pWaterTexData[(iX%128)+(iY%128)*128];
				}
		}
		else
		{
			// Fill the preview with the heightmap image
			for (int iY=bounds.top; iY < bounds.bottom; iY++)
				for (int iX=bounds.left; iX < bounds.right; iX++)
				{
					// Use the height value as grayscale
					uint32 val = ftoi(pH[iX + iY*width] * fByteScale);
					pBitmapData[iX + iY*width] = RGB(val,val,val);
				}
		}
	}

	return res;
}

void CHeightmap::GenerateTerrain(const SNoiseParams &noiseParam)
{
	////////////////////////////////////////////////////////////////////////
	// Generate a new terrain with the parameters stored in sParam
	////////////////////////////////////////////////////////////////////////

	unsigned int i, j;
	CDynamicArray2D cHeightmap(GetWidth(), GetHeight());
	CNoise cNoise;
	float fYScale = 255.0f;
	float *pArrayShortcut = NULL;
	DWORD *pImageData = NULL;
	DWORD *pImageDataStart = NULL;
	
	SNoiseParams sParam = noiseParam;

	ASSERT(sParam.iWidth == m_iWidth && sParam.iHeight == m_iHeight);

	//////////////////////////////////////////////////////////////////////
	// Generate the noise array
	//////////////////////////////////////////////////////////////////////

 	AfxGetMainWnd()->BeginWaitCursor();

	CLogFile::WriteLine("Noise...");

	// Set the random value
	srand(sParam.iRandom);

	// Process layers
	for (i=0; i<sParam.iPasses; i++)
	{
		// Apply the fractal noise function to the array
		cNoise.FracSynthPass(&cHeightmap, sParam.fFrequency, fYScale, 
			sParam.iWidth, sParam.iHeight, FALSE);

		// Modify noise generation parameters
		sParam.fFrequency *= sParam.fFrequencyStep;
		if(sParam.fFrequency>16000.f)
			sParam.fFrequency = 16000.f;
		fYScale *= sParam.fFade;	
	}

	//////////////////////////////////////////////////////////////////////
	// Store the generated terrain in the heightmap
	//////////////////////////////////////////////////////////////////////

	for (j=0; j<m_iHeight; j++)
		for (i=0; i<m_iWidth; i++)
		{
			//SetXY(i, j, cHeightmap.m_Array[i][j]);
			SetXY(i, j, MAX(MIN(cHeightmap.m_Array[i][j], 512000.0f), -512000.0f));
		}

	//////////////////////////////////////////////////////////////////////
	// Perform some modifications on the heightmap
	//////////////////////////////////////////////////////////////////////

	// Smooth the heightmap and normalize it
	for (i=0; i<sParam.iSmoothness; i++)
		Smooth();

	Normalize();
	MakeIsle();
			
	//////////////////////////////////////////////////////////////////////
	// Finished
	//////////////////////////////////////////////////////////////////////

	// We modified the heightmap.
	SetModified();

	// All layers need to be generated from scratch
	CTerrainManager::GetTerrainManager().InvalidateLayers();

	AfxGetMainWnd()->EndWaitCursor();
}


float CHeightmap::CalcHeightScale() const
{
	return 1.0f / (float)(GetWidth() * GetUnitSize());
}

void CHeightmap::SmoothSlope()
{
	//////////////////////////////////////////////////////////////////////
	// Remove areas with high slope from the heightmap
	//////////////////////////////////////////////////////////////////////

	UINT iCurPos;
	float fAverage;
	unsigned int i, j;

	CLogFile::WriteLine("Smoothing the slope of the heightmap...");

	// Remove the high slope areas (horizontal)
	for (j=1; j<m_iHeight - 1; j++)
	{
		// Precalculate for better speed
		iCurPos = j * m_iWidth + 1;

		for (i=1; i<m_iWidth - 1; i++)
		{
			// Next pixel
			iCurPos++;

			// Get the average value for this area
			fAverage = 
				(m_pHeightmap[iCurPos]                + m_pHeightmap[iCurPos + 1]            + m_pHeightmap[iCurPos + m_iWidth] +
				 m_pHeightmap[iCurPos + m_iWidth + 1] + m_pHeightmap[iCurPos - 1]            + m_pHeightmap[iCurPos - m_iWidth] + 
				 m_pHeightmap[iCurPos - m_iWidth - 1] + m_pHeightmap[iCurPos - m_iWidth + 1] + m_pHeightmap[iCurPos + m_iWidth - 1])
				 * 0.11111111111f;

			// Clamp the surrounding values to the given level
			ClampToAverage(&m_pHeightmap[iCurPos], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos + 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos + m_iWidth], fAverage);
			// TODO: ClampToAverage(&m_pHeightmap[iCurPos + m_iWidth + 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - m_iWidth], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - m_iWidth - 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - m_iWidth + 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos + m_iWidth - 1], fAverage);
		}
	}

	// Remove the high slope areas (vertical)
	for (i=1; i<m_iWidth - 1; i++)
	{
		// Precalculate for better speed
		iCurPos = i;

		for (j=1; j<m_iHeight - 1; j++)
		{
			// Next pixel
			iCurPos += m_iWidth;

			// Get the average value for this area
			fAverage = 
				(m_pHeightmap[iCurPos]                + m_pHeightmap[iCurPos + 1]            + m_pHeightmap[iCurPos + m_iWidth] +
				 m_pHeightmap[iCurPos + m_iWidth + 1] + m_pHeightmap[iCurPos - 1]            + m_pHeightmap[iCurPos - m_iWidth] + 
				 m_pHeightmap[iCurPos - m_iWidth - 1] + m_pHeightmap[iCurPos - m_iWidth + 1] + m_pHeightmap[iCurPos + m_iWidth - 1])
				 * 0.11111111111f;

			// Clamp the surrounding values to the given level
			ClampToAverage(&m_pHeightmap[iCurPos], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos + 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos + m_iWidth], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos + m_iWidth + 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - m_iWidth], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - m_iWidth - 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos - m_iWidth + 1], fAverage);
			ClampToAverage(&m_pHeightmap[iCurPos + m_iWidth - 1], fAverage);
		}
	}

	// We modified the heightmap.
	SetModified();
}

void CHeightmap::ClampToAverage(t_hmap *pValue, float fAverage)
{
	//////////////////////////////////////////////////////////////////////
	// Used during slope removal to clamp height values into a normalized
	// rage
	//////////////////////////////////////////////////////////////////////

	float fClampedVal;

	// Does the heightvalue differ heavily from the average value ?
	if (fabs(*pValue - fAverage) > fAverage * 0.001f)
	{
		// Negativ / Positiv ?
		if (*pValue < fAverage)
			fClampedVal = fAverage - (fAverage * 0.001f);
		else
			fClampedVal = fAverage + (fAverage * 0.001f);
		
		// Renormalize it
		ClampHeight(fClampedVal);

		*pValue = fClampedVal;
	}
}

void CHeightmap::MakeIsle()
{
	//////////////////////////////////////////////////////////////////////
	// Convert any terrain into an isle
	//////////////////////////////////////////////////////////////////////

	int i, j;
	t_hmap *pHeightmapData = m_pHeightmap;
//	UINT m_iHeightmapDiag;
	float fDeltaX, fDeltaY;
	float fDistance;
	float fCurHeight, fFade;
	
	CLogFile::WriteLine("Modifying heightmap to an isle...");

	// Calculate the length of the diagonale trough the heightmap
//	m_iHeightmapDiag = (UINT) sqrt(GetWidth() * GetWidth() +
//		GetHeight() * GetHeight());
	float fMaxDistance = sqrtf((GetWidth()/2) * (GetWidth()/2) +	(GetHeight()/2) * (GetHeight()/2));

	for (j=0; j<m_iHeight; j++)
	{
		// Calculate the distance delta
		fDeltaY = (float)abs( (int)(j - m_iHeight/2) );

		for (i=0; i<m_iWidth; i++)
		{
			// Calculate the distance delta
			fDeltaX = (float)abs( (int)(i-m_iWidth/2) );
			
			// Calculate the distance
			fDistance = (float) sqrt(fDeltaX * fDeltaX + fDeltaY * fDeltaY);

			// Calculate the fade-off
//			fFade = __min(0.5f, __min(sinf((float) i / ((float) m_iWidth / 3.12f)),
//				sinf((float) j / ((float) m_iHeight / 3.12f))));
//			float fCos=cosf(__clamp(fDistance/fMaxDistance*3.1416f, 0.0f, 3.1415f));
//			fFade = __clamp(fCos*0.5f+0.5f, 0.0f, 1.0f);
//			float fCosX=cosf(__clamp(sinf((float)i/(float)m_iWidth*3.1416f)*3.1416f, 0.0f, 3.1415f));
//			float fCosY=cosf(__clamp(sinf((float)j/(float)m_iHeight*3.1416f)*3.1416f, 0.0f, 3.1415f));
			float fCosX=sinf((float)i/(float)m_iWidth*3.1416f);
			float fCosY=sinf((float)j/(float)m_iHeight*3.1416f);
			fFade=fCosX*fCosY;
//			fFade = __clamp((fCosX*0.5f+0.5f)*(fCosY*0.5f+0.5f), 0.0f, 1.0f);

			// Only apply heavy modification to the borders of the island
//			fFade = fFade * fFade + (1.0f - fFade) * (fFade * 2);
			fFade=1.0-((1.0f-fFade)*(1.0f-fFade));

			// Modify the value
			fCurHeight = *pHeightmapData;
//			fCurHeight += fFade * 10.0f;
//	    fCurHeight *= sqrtf(fFade);
			fCurHeight *= fFade;
			
			// Clamp
			ClampHeight(fCurHeight);

			// Write the value back and andvance
			*pHeightmapData++ = fCurHeight;
		}
	}

	// We modified the heightmap.
	SetModified();
}

void CHeightmap::Flatten(float fFactor)
{
	////////////////////////////////////////////////////////////////////////
	// Increase the number of flat areas on the heightmap (TODO: Fix !)
	////////////////////////////////////////////////////////////////////////

	t_hmap *pHeightmapData = m_pHeightmap;
	t_hmap *pHeightmapDataEnd = &m_pHeightmap[m_iWidth * m_iHeight];
	float fRes;

	CLogFile::WriteLine("Flattening heightmap...");

	// Perform the conversion
	while (pHeightmapData != pHeightmapDataEnd)
	{
		// Get the exponential value for this height value
		fRes = ExpCurve(*pHeightmapData, 128.0f, 0.985f);

		// Is this part of the landscape a potential flat area ?
		// Only modify parts of the landscape that are above the water level
		if (fRes < 100 && *pHeightmapData > m_fWaterLevel)
		{
			// Yes, apply the factor to it
			*pHeightmapData = (t_hmap) (*pHeightmapData * fFactor);

			// When we use a factor greater than 0.5, we don't want to drop below
			// the water level
			*pHeightmapData++ = __max(m_fWaterLevel, *pHeightmapData);
		}
		else
		{
			// No, use the exponential function to make smooth transitions
			*pHeightmapData++ = (t_hmap) fRes;
		}
	}

	// We modified the heightmap.
	SetModified();
}

float CHeightmap::ExpCurve(float v, float fCover, float fSharpness)
{
	//////////////////////////////////////////////////////////////////////
	// Exponential function
	//////////////////////////////////////////////////////////////////////

	float c;

	c = v - fCover;

	if (c < 0)
		c = 0;

	return m_fMaxHeight - (float) ((pow(fSharpness, c)) * m_fMaxHeight);
}

void CHeightmap::MakeBeaches()
{
	//////////////////////////////////////////////////////////////////////
	// Create flat areas around beaches
	//////////////////////////////////////////////////////////////////////

  CTerrainFormulaDlg TerrainFormulaDlg;  
  TerrainFormulaDlg.m_dParam1 = 5;
  TerrainFormulaDlg.m_dParam2 = 1000;
  TerrainFormulaDlg.m_dParam3 = 1;  
  if(TerrainFormulaDlg.DoModal()!=IDOK)
    return;

	unsigned int i, j;
	t_hmap *pHeightmapData = NULL;
	t_hmap *pHeightmapDataStart = NULL;
	double dCurHeight;
	
	CLogFile::WriteLine("Applaing formula ...");

	// Get the water level
	double dWaterLevel = m_fWaterLevel;

	// Make the beaches
	for (j=0; j<m_iHeight; j++)
	{
		for (i=0; i<m_iWidth; i++)
		{
			dCurHeight = m_pHeightmap[i + j * m_iWidth];


			// Center water level at zero
			dCurHeight -= dWaterLevel;
    
      // do nothing with small values but increase big values
      dCurHeight = dCurHeight * (1.0 + fabs(dCurHeight)*TerrainFormulaDlg.m_dParam1);    

      // scale back (can be automated)
      dCurHeight /= TerrainFormulaDlg.m_dParam2;
      
			// Convert the coordinates back to the old range
			dCurHeight += dWaterLevel;

      // move flat area up out of water
      dCurHeight += TerrainFormulaDlg.m_dParam3;
  
      // check range
			if(dCurHeight > m_fMaxHeight)
        dCurHeight = m_fMaxHeight;
      else if(dCurHeight<0)
        dCurHeight=0;
	
			// Write the value back and andvance to the next pixel
			m_pHeightmap[i + j * m_iWidth] = dCurHeight;
		}
	}

  m_pHeightmap[0]=0;
  m_pHeightmap[1]=255;

	// Normalize because the beach creation distorted the value range
///	Normalize();

	// We modified the heightmap.
	SetModified();
}

void CHeightmap::LowerRange(float fFactor)
{
	//////////////////////////////////////////////////////////////////////
	// Lower the value range of the heightmap, effectively making it
	// more flat
	//////////////////////////////////////////////////////////////////////

	unsigned int i;
	float fWaterHeight = m_fWaterLevel;

	CLogFile::WriteLine("Lowering range...");

	// Lower the range, make sure we don't put anything below the water level
	for (i=0; i<m_iWidth * m_iHeight; i++)
		m_pHeightmap[i] = ((m_pHeightmap[i] - fWaterHeight) * fFactor) + fWaterHeight;

	// We modified the heightmap.
	SetModified();
}

void CHeightmap::Randomize()
{
	////////////////////////////////////////////////////////////////////////
	// Add a small amount of random noise
	////////////////////////////////////////////////////////////////////////

	unsigned int i;

	CLogFile::WriteLine("Lowering range...");

	// Add the noise
	for (i=0; i<m_iWidth * m_iHeight; i++)
		m_pHeightmap[i] += (float) rand() / RAND_MAX*8.0f - 4.0f;

	// Normalize because we might have valid the valid range
	Normalize();

	// We modified the heightmap.
	SetModified();
}

void CHeightmap::DrawSpot(unsigned long iX, unsigned long iY,
						              uint8 iWidth, float fAddVal, 
						              float fSetToHeight, bool bAddNoise)
{
	////////////////////////////////////////////////////////////////////////
	// Draw an attenuated spot on the map
	////////////////////////////////////////////////////////////////////////
	SW_TEST_TERRAIN_MOD( iX-iWidth,iY-iWidth,iX+iWidth,iY+iWidth, true, true );

	long i, j;
	long iPosX, iPosY, iIndex;
	float fMaxDist, fAttenuation, fJSquared;
	float fCurHeight;
	
	assert(m_pNoise);

	if (bAddNoise)
		InitNoise();

	// Calculate the maximum distance
	fMaxDist = sqrtf((float) ((iWidth / 2) * (iWidth / 2) + (iWidth / 2) * (iWidth / 2)));

	if (GetIEditor()->IsUndoRecording())
		GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-iWidth,iY-iWidth,iWidth*2,iWidth*2,this) );

	for (j=(long) -iWidth; j<iWidth; j++)
	{
		// Precalculate
		iPosY = iY + j;
		fJSquared = (float) (j * j);
		
		for (i=(long) -iWidth; i<iWidth; i++)
		{
			// Calculate the position
			iPosX = iX + i;
			
			// Skip invalid locations
			if (iPosX < 0 || iPosY < 0 || 
				iPosX > (long) m_iWidth - 1 || iPosY > (long) m_iHeight - 1)
			{
				continue;
			}

			// Calculate the array index
			iIndex = iPosX + iPosY * m_iWidth;

			// Calculate attenuation factor
			fAttenuation = 1.0f - __min(1.0f, sqrtf((float) (i * i + fJSquared)) / fMaxDist);

			// Which drawing mode are we in ?
			if (fSetToHeight >= 0.0f)
			{
				// Set to height mode, modify the location towards the specified height
				fCurHeight = m_pHeightmap[iIndex];
				m_pHeightmap[iIndex] *= 4.0f;
				m_pHeightmap[iIndex] += (1.0f - fAttenuation) * fCurHeight + fAttenuation * fSetToHeight;
				m_pHeightmap[iIndex] /= 5.0f;
			}
			else if (bAddNoise)
			{
				// Noise brush
				if (fAddVal > 0.0f)
				{
					m_pHeightmap[iIndex] += fAddVal/100 * 
						((float) fabs(m_pNoise->m_Array[iPosX % NOISE_ARRAY_SIZE][iPosY % NOISE_ARRAY_SIZE]))
						* fAttenuation;
				}
				else
				{
					m_pHeightmap[iIndex] += fAddVal/100 *
						(float) (-fabs(m_pNoise->m_Array[iPosX % NOISE_ARRAY_SIZE][iPosY % NOISE_ARRAY_SIZE]))
						* fAttenuation;
				}
			}
			else
			{
				// No, modify the location with a normal brush
				m_pHeightmap[iIndex] += fAddVal * fAttenuation;
			}

			// Clamp
			ClampHeight(m_pHeightmap[iIndex]);
		}
	}

	// We modified the heightmap.
	SetModified( iX-iWidth,iY-iWidth,iX+iWidth,iY+iWidth );
}

void CHeightmap::DrawSpot2( int iX, int iY, int radius,float insideRadius,float fHeight,float fHardness,bool bAddNoise,float noiseFreq,float noiseScale )
{
	////////////////////////////////////////////////////////////////////////
	// Draw an attenuated spot on the map
	////////////////////////////////////////////////////////////////////////
	int i, j;
	int iPosX, iPosY, iIndex;
	float fMaxDist, fAttenuation, fYSquared;
	float fCurHeight;

	if (bAddNoise)
		InitNoise();

	if (GetIEditor()->IsUndoRecording())
		GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-radius,iY-radius,radius*2,radius*2,this) );

	// Calculate the maximum distance
	fMaxDist = radius;

	for (j=(long) -radius; j<radius; j++)
	{
		// Precalculate
		iPosY = iY + j;
		fYSquared = (float) (j * j);
		
		for (i=(long) -radius; i<radius; i++)
		{
			// Calculate the position
			iPosX = iX + i;
			
			// Skip invalid locations
			if (iPosX < 0 || iPosY < 0 ||	iPosX > m_iWidth - 1 || iPosY > m_iHeight - 1)
				continue;

			// Only circle.
			float dist = sqrtf(fYSquared + i*i);
			if (dist > fMaxDist)
				continue;

			// Calculate the array index
			iIndex = iPosX + iPosY * m_iWidth;

			// Calculate attenuation factor
			if (dist <= insideRadius)
				fAttenuation = 1.0f;
			else
				fAttenuation = 1.0f - __min(1.0f, (dist-insideRadius)/fMaxDist);

			// Set to height mode, modify the location towards the specified height
			fCurHeight = m_pHeightmap[iIndex];
			float dh = fHeight - fCurHeight;

			float h = fCurHeight + (fAttenuation)*dh*fHardness;
			

			if (bAddNoise)
			{
				int nx = ftoi(iPosX*noiseFreq) % NOISE_ARRAY_SIZE;
				int ny = ftoi(iPosY*noiseFreq) % NOISE_ARRAY_SIZE;
				float noise = m_pNoise->m_Array[nx][ny];
				h += (float)(noise)*fAttenuation*noiseScale;
			}
			

			// Clamp
			ClampHeight(h);
			m_pHeightmap[iIndex] = h;
		}
	}

	// We modified the heightmap.
	SetModified( iX-radius,iY-radius,iX+radius,iY+radius );
}

void CHeightmap::RiseLowerSpot( int iX, int iY, int radius,float insideRadius,float fHeight,float fHardness,bool bAddNoise,float noiseFreq,float noiseScale )
{
	////////////////////////////////////////////////////////////////////////
	// Draw an attenuated spot on the map
	////////////////////////////////////////////////////////////////////////
	int i, j;
	int iPosX, iPosY, iIndex;
	float fMaxDist, fAttenuation, fYSquared;
	float fCurHeight;

	if (bAddNoise)
		InitNoise();

	if (GetIEditor()->IsUndoRecording())
		GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-radius,iY-radius,radius*2,radius*2,this) );

	// Calculate the maximum distance
	fMaxDist = radius;

	for (j=(long) -radius; j<radius; j++)
	{
		// Precalculate
		iPosY = iY + j;
		fYSquared = (float) (j * j);

		for (i=(long) -radius; i<radius; i++)
		{
			// Calculate the position
			iPosX = iX + i;

			// Skip invalid locations
			if (iPosX < 0 || iPosY < 0 ||	iPosX > m_iWidth - 1 || iPosY > m_iHeight - 1)
				continue;

			// Only circle.
			float dist = sqrtf(fYSquared + i*i);
			if (dist > fMaxDist)
				continue;

			// Calculate the array index
			iIndex = iPosX + iPosY * m_iWidth;

			// Calculate attenuation factor
			if (dist <= insideRadius)
				fAttenuation = 1.0f;
			else
				fAttenuation = 1.0f - __min(1.0f, (dist-insideRadius)/fMaxDist);

			// Set to height mode, modify the location towards the specified height
			fCurHeight = m_pHeightmap[iIndex];
			float dh = fHeight;

			float h = fCurHeight + (fAttenuation)*dh*fHardness;


			if (bAddNoise)
			{
				int nx = ftoi(iPosX*noiseFreq) % NOISE_ARRAY_SIZE;
				int ny = ftoi(iPosY*noiseFreq) % NOISE_ARRAY_SIZE;
				float noise = m_pNoise->m_Array[nx][ny];
				h += (float)(noise)*fAttenuation*noiseScale;
			}


			// Clamp
			ClampHeight(h);

			m_pHeightmap[iIndex] = h;
		}
	}

	// We modified the heightmap.
	SetModified( iX-radius,iY-radius,iX+radius,iY+radius );
}

void CHeightmap::SmoothSpot( int iX, int iY, int radius, float fHeight,float fHardness )
{
	////////////////////////////////////////////////////////////////////////
	// Draw an attenuated spot on the map
	////////////////////////////////////////////////////////////////////////
	int i, j;
	int iPosX, iPosY;
	float fMaxDist, fYSquared;

	if (GetIEditor()->IsUndoRecording())
		GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-radius,iY-radius,radius*2,radius*2,this) );

	// Calculate the maximum distance
	fMaxDist = radius;

	for (j=(long) -radius; j<radius; j++)
	{
		// Precalculate
		iPosY = iY + j;
		fYSquared = (float) (j * j);

		// Skip invalid locations
		if (iPosY < 1 || iPosY > m_iHeight-2)
				continue;
		
		for (i=(long) -radius; i<radius; i++)
		{
			// Calculate the position
			iPosX = iX + i;
			
			// Skip invalid locations
			if (iPosX < 1 || iPosX > m_iWidth-2)
				continue;

			// Only circle.
			float dist = sqrtf(fYSquared + i*i);
			if (dist > fMaxDist)
				continue;

			int pos = iPosX + iPosY*m_iWidth;
			float h;
			h =	(m_pHeightmap[pos] +
					 m_pHeightmap[pos+1] +
					 m_pHeightmap[pos-1] +
					 m_pHeightmap[pos+m_iWidth] +
					 m_pHeightmap[pos-m_iWidth] + 
					 m_pHeightmap[pos+1+m_iWidth] +
					 m_pHeightmap[pos+1-m_iWidth] +
					 m_pHeightmap[pos-1+m_iWidth] +
					 m_pHeightmap[pos-1-m_iWidth])
					/ 9.0f;

			float currH = m_pHeightmap[pos];
			m_pHeightmap[pos] = currH + (h-currH)*fHardness;
		}
	}

	// We modified the heightmap.
	SetModified( iX-radius,iY-radius,iX+radius,iY+radius );
}

float GetMedian (int count, float* arr)
{
	std::sort(arr, arr + count);
	return arr[count / 2];
}

struct RampSegment
{
	Vec2 p1, p2;
	float h1, h2;
	Vec2 offsetP1, offsetP2;
};

Vec2 OffsetCurveIntersection (float* pA, float* pB, Vec2 p1, Vec2 p2, Vec2 p3, float offset)
{
	//CryLogAlways("offset = %f, points = [(%f, %f) (%f, %f) (%f, %f)]", offset, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
	Vec2 a = (p1 - p2).GetNormalized();
	Vec2 b = (p3 - p2).GetNormalized();
	float ab = a.Dot(b);

	if (ab < -0.999f)
	{
		*pA = 1.0f;
		*pB = 0.0f;
		return p2 + 0.5f * (Vec2(a.y - b.y, b.x - a.x)) * offset;
	}
	else
	{
		float x = offset / (ab * b - a).GetLength();
		float y = offset / (ab * a - b).GetLength();
		//CryLogAlways("  x = %f, y = %f", x, y);
		Vec2 result = p2 + x * a + y * b;
		*pA = (result - p1).Dot(p2 - p1) / (p2 - p1).Dot(p2 - p1);
		*pB = (result - p2).Dot(p3 - p2) / (p3 - p2).Dot(p3 - p2);
		return result;
	}
}

void CreateSegments (std::vector<RampSegment>& segs, float h1, float h2, Vec2* points, int count, float innerRadius)
{
	segs.clear();
	for (int i = 0; i < count - 1; ++i)
	{
		RampSegment seg;
		seg.p1 = points[i];
		seg.p2 = points[i + 1];
		seg.offsetP1 = seg.p1;
		seg.offsetP2 = seg.p2;
		segs.push_back(seg);
	}
	for (int i = 0; i < count - 2; ++i)
	{
		RampSegment* pSeg1 = &segs[i];
		RampSegment* pSeg2 = &segs[i + 1];
		float a, b;
		Vec2 offsetP = OffsetCurveIntersection(&a, &b, pSeg1->p1, pSeg1->p2, pSeg2->p2, innerRadius);
		pSeg1->offsetP2 = offsetP;
		pSeg1->p2 = pSeg1->p1 + a * (pSeg1->p2 - pSeg1->p1);
		pSeg2->offsetP1 = offsetP;
		pSeg2->p1 = pSeg2->p1 + b * (pSeg2->p2 - pSeg2->p1);
	}
	float totalLength = 0.0f;
	for (int i = 0; i < count - 1; ++i)
		totalLength += (segs[i].p2 - segs[i].p1).GetLength();
	float len = 0.0f;
	for (int i = 0; i < count - 1; ++i)
	{
		segs[i].h1 = h1 + (len / totalLength) * (h2 - h1);
		len += (segs[i].p2 - segs[i].p1).GetLength();
		segs[i].h2 = h1 + (len / totalLength) * (h2 - h1);
	}
}

void InterpolateRamp (float* pHeight, float* pDist, Vec2 pos, const std::vector<RampSegment>& segs, float innerRadius, bool smoothHeight)
{
	float bestDistSquared = FLT_MAX;
	int segmentIndex = 0;
	float segmentParam = 0;

	for (int seg = 0; seg < segs.size(); ++seg)
	{
		const RampSegment* pSeg = &segs[seg];

		Vec2 p1 = pSeg->p1;
		Vec2 p2 = pSeg->p2;
		Vec2 d = p2 - p1;
		float distSquared = 0;
		float param = d.Dot(pos - p1) / d.Dot(d);

		if (param < 0)
		{
			distSquared = (pos - p1).GetLength2();
			param = 0.0f;
		}
		else if (param > 1)
		{
			distSquared = (pos - p2).GetLength2();
			param = 1.0f;
		}
		else
		{
			distSquared = (pos - p1 - param * d).GetLength2();
		}
		if (distSquared < bestDistSquared)
		{
			bestDistSquared = distSquared;
			segmentIndex = seg;
			segmentParam = param;
		}
	}

	const RampSegment* pSeg = &segs[segmentIndex];
	if (segmentIndex > 0 && segmentParam == 0.0f)
		*pDist = fabsf((pSeg->offsetP1 - pos).GetLength() - innerRadius);
	else if (segmentIndex < segs.size() - 1 && segmentParam == 1.0f)
		*pDist = fabsf((pSeg->offsetP2 - pos).GetLength() - innerRadius);
	else
		*pDist = sqrtf(bestDistSquared);

	float t = segmentParam;
	if (smoothHeight)
		t = t * t * (3.0f - 2.0f * t);
	*pHeight = pSeg->h1 + t * (pSeg->h2 - pSeg->h1);
}

void CHeightmap::Hold()
{
	////////////////////////////////////////////////////////////////////////
	// Save a backup copy of the heightmap
	////////////////////////////////////////////////////////////////////////

	FILE *hFile = NULL;

	CLogFile::WriteLine("Saving temporary copy of the heightmap");

	AfxGetMainWnd()->BeginWaitCursor();

	// Open the hold / fetch file
	hFile = gEnv->pCryPak->FOpen(HEIGHTMAP_HOLD_FETCH_FILE, "wb");
	ASSERT(hFile);
	if (hFile)
	{
		// Write the dimensions
		VERIFY(fwrite(&m_iWidth, sizeof(m_iWidth), 1, hFile));
		VERIFY(fwrite(&m_iHeight, sizeof(m_iHeight), 1, hFile));

		// Write the data
		VERIFY(fwrite(m_pHeightmap, sizeof(t_hmap), m_iWidth * m_iHeight, hFile));

		//! Write the info.
		VERIFY(fwrite( m_LayerIdBitmap.GetData(),sizeof(unsigned char), m_LayerIdBitmap.GetSize(), hFile));

		fclose(hFile);
	}

	AfxGetMainWnd()->EndWaitCursor();
}

void CHeightmap::Fetch()
{
	////////////////////////////////////////////////////////////////////////
	// Read a backup copy of the heightmap
	////////////////////////////////////////////////////////////////////////

	CLogFile::WriteLine("Loading temporary copy of the heightmap");

	AfxGetMainWnd()->BeginWaitCursor();

	if (!Read(HEIGHTMAP_HOLD_FETCH_FILE))
	{
		AfxMessageBox("You need to use 'Hold' before 'Fetch' !");
		return;
	}

	AfxGetMainWnd()->EndWaitCursor();
	SetModified();
}

bool CHeightmap::Read(CString strFileName)
{
	////////////////////////////////////////////////////////////////////////
	// Load a heightmap from a file
	////////////////////////////////////////////////////////////////////////

	FILE *hFile = NULL;
	uint64 iWidth, iHeight;

	if (strFileName.IsEmpty())
		return false;

	// Open the hold / fetch file
	hFile = gEnv->pCryPak->FOpen(strFileName.GetBuffer(0), "rb");
	
	if (!hFile)
		return false;

	// Read the dimensions
	VERIFY(fread(&iWidth, sizeof(iWidth), 1, hFile));
	VERIFY(fread(&iHeight, sizeof(iHeight), 1, hFile));

	// Resize the heightmap
	Resize(iWidth, iHeight,m_unitSize);

	// Load the data
	VERIFY(fread(m_pHeightmap, sizeof(t_hmap), m_iWidth * m_iHeight, hFile));

	//! Write the info.
	m_LayerIdBitmap.Allocate( m_iWidth,m_iHeight );
	VERIFY(fread( m_LayerIdBitmap.GetData(),sizeof(unsigned char), m_LayerIdBitmap.GetSize(), hFile));

	fclose(hFile);

	return true;
}

void CHeightmap::InitNoise()
{
	////////////////////////////////////////////////////////////////////////
	// Initialize the noise array
	////////////////////////////////////////////////////////////////////////
	if (m_pNoise)
		return;


	CNoise cNoise;
	static bool bFirstQuery = true;
	float fFrequency = 6.0f;
	float fFrequencyStep = 2.0f;
	float fYScale = 1.0f;
	float fFade = 0.46f;
	float fLowestPoint = +FLT_MAX, fHighestPoint = -FLT_MAX;
	float fValueRange;
	unsigned int i, j;

	assert(!m_pNoise);

	// Allocate a new array class to 
	m_pNoise = new CDynamicArray2D(NOISE_ARRAY_SIZE, NOISE_ARRAY_SIZE);
	
	// Process layers
	for (i=0; i<8; i++)
	{
		// Apply the fractal noise function to the array
		cNoise.FracSynthPass(m_pNoise, fFrequency, fYScale, NOISE_ARRAY_SIZE, NOISE_ARRAY_SIZE, TRUE);

		// Modify noise generation parameters
		fFrequency *= fFrequencyStep;
		fYScale *= fFade;	
	}

	// Find the value range
	for (i=0; i<NOISE_ARRAY_SIZE; i++)
		for (j=0; j<NOISE_ARRAY_SIZE; j++)
		{
			fLowestPoint = __min(fLowestPoint, m_pNoise->m_Array[i][j]);
			fHighestPoint = __max(fHighestPoint, m_pNoise->m_Array[i][j]);
		}

	// Storing the value range in this way saves us a division and a multiplication
	fValueRange = NOISE_RANGE / (fHighestPoint - fLowestPoint);

	// Normalize the heightmap
	for (i=0; i<NOISE_ARRAY_SIZE; i++)
		for (j=0; j<NOISE_ARRAY_SIZE; j++)
		{
			m_pNoise->m_Array[i][j] -= fLowestPoint;
			m_pNoise->m_Array[i][j] *= fValueRange;
#ifdef SEG_WORLD
			m_pNoise->m_Array[i][j] -= 127.5f;
#endif

			// Keep signed / unsigned balance
			//m_pNoise->m_Array[i][j] += 2500.0f/255.0f;
		}
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::CalcSurfaceTypes( const CRect *rect )
{
	int i;
	bool first = true;

	CRect rc;

	CFloatImage hmap;
	hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight );

	if (rect)
		rc = *rect;
	else
		rc.SetRect( 0,0,m_iWidth,m_iHeight );

	// Generate the masks
	CCryEditDoc *doc = GetIEditor()->GetDocument();
	int numLayers = CTerrainManager::GetTerrainManager().GetLayerCount();
	for (i = 0; i < numLayers; i++)
	{
		CLayer *pLayer = CTerrainManager::GetTerrainManager().GetLayer(i);
		
		if (!pLayer->IsInUse())
			continue;

		uint32 dwLayerId = pLayer->GetOrRequestLayerId();

		if(first)
		{
			first = false;

			// For every pixel of layer update surface type.
			for (uint32 y = rc.top; y < rc.bottom; y++)
			{
				int yp = y*m_iWidth;
				for (uint32 x = rc.left; x < rc.right; x++)
					SetLayerIdAt(x,y,dwLayerId);
			}
		}
		else
		{
			// Assume that layer mask is at size of full resolution texture.
			CByteImage &layerMask = pLayer->GetMask();
			if (!layerMask.IsValid())
				continue;
			
			int layerWidth = layerMask.GetWidth();
			int layerHeight = layerMask.GetHeight();
			float xScale = (float)layerWidth / m_iWidth;
			float yScale = (float)layerHeight / m_iHeight;

			uint8 *pLayerMask = layerMask.GetData();
			
			// For every pixel of layer update surface type.
			for (uint32 y = rc.top; y < rc.bottom; y++)
			{
				for (uint32 x = rc.left; x < rc.right; x++)
				{
					uint8 a = pLayerMask[ftoi(x*xScale + layerWidth*y*yScale)];
					if (a > OVVERIDE_LAYER_SURFACETYPE_FROM)
					{					
						SetLayerIdAt(x,y,dwLayerId);
					}
				}
			}
		}
	}
}


void CHeightmap::UpdateEngineTerrain( bool bOnlyElevation,bool boUpdateReloadSurfacertypes )
{
  LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);

	if (boUpdateReloadSurfacertypes)
	{
		CTerrainManager::GetTerrainManager().ReloadSurfaceTypes();
	}

	UpdateEngineTerrain( 0,0,m_iWidth,m_iHeight,true,!bOnlyElevation );
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::UpdateEngineTerrain( int x1, int y1, int areaSize, int _height, bool bElevation, bool bInfoBits )
{
  LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);

	I3DEngine* p3DEngine = GetIEditor()->Get3DEngine();

	const int nHeightMapUnitSize = p3DEngine->GetHeightMapUnitSize();

  if( p3DEngine->GetITerrain() )
    if( ICVar * pHMECvar = gEnv->pConsole->GetCVar("e_VoxTerHeightmapEditing") )
      if( pHMECvar->GetIVal() )
  {
    // copy modified heightmap into voxel terrain
    Sphere wsSphere(Vec3(y1*nHeightMapUnitSize+areaSize*nHeightMapUnitSize/2, x1*nHeightMapUnitSize+areaSize*nHeightMapUnitSize/2, 0), areaSize*nHeightMapUnitSize/2);
    wsSphere.center.z = p3DEngine->GetTerrainElevation(wsSphere.center.x,wsSphere.center.y);
    wsSphere.radius *= 1.2f;
    if( x1 && y1 )
      gEnv->p3DEngine->Voxel_Paint(wsSphere.center, wsSphere.radius, -1, Vec3(0,0,0), bElevation ? eveoCopyTerrainPos : eveoCopyTerrainNeg, evbsSphere, evetVoxelObjects, NULL, 32.f);
  }

	// update terrain by square blocks aligned to terrain sector size

	int nSecSize = p3DEngine->GetTerrainSectorSize()/p3DEngine->GetHeightMapUnitSize();
	if (nSecSize == 0)
		return;
	
	const int originalInputAreaSize = areaSize;
	const int originalInputX1 = x1;
	const int originalInputY1 = y1;

	int nTerrSize = m_iWidth;

	if (!CSWManager::IsActive()) {
	areaSize = ceil(areaSize/(float)nSecSize+1)*nSecSize;
	areaSize = CLAMP(areaSize, 0, nTerrSize);
	}

	x1 = floor(x1/(float)nSecSize)*nSecSize;
	y1 = floor(y1/(float)nSecSize)*nSecSize;
	x1 = CLAMP(x1, 0, (nTerrSize-areaSize));
	y1 = CLAMP(y1, 0, (nTerrSize-areaSize));

	int x2 = x1+areaSize;
	int y2 = y1+areaSize;
	x2 = CLAMP(x2, areaSize, nTerrSize);
	y2 = CLAMP(y2, areaSize, nTerrSize);

	int w, h;
	w = x2-x1;
	h = y2-y1;

	if(w<=0 || h<=0)
		return;

	TImage<unsigned char> image;
	image.Allocate( w, h );
	unsigned char *terrainBlock = image.GetData();

	// fill image with surface type info
	{
		uint8 LayerIdToDetailId[256];

		// to speed up the following loops
		for(uint32 dwI=0;dwI<256;++dwI)
			LayerIdToDetailId[dwI] = CTerrainManager::GetTerrainManager().GetDetailIdLayerFromLayerId(dwI) & SURFACE_TYPE_MASK;

		for (int y = y1; y < y2; y++)
		for (int x = x1; x < x2; x++)
		{
			int ncell = (y-y1)*w + (x-x1);
			assert(ncell>=0 && ncell<w*h);
			unsigned char *p = &terrainBlock[ncell];

			if(IsHoleAt(x,y))
			{
				*p = SURFACE_TYPE_MASK;
			}
			else
			{
				uint32 dwLayerId = GetLayerIdAt(x,y);			assert(dwLayerId<=0xff);
				if(LayerIdToDetailId[dwLayerId] == (0xffffffff & SURFACE_TYPE_MASK))
					*p = 0;
				else
					*p = LayerIdToDetailId[dwLayerId];
			}
		}
	}
	
	if (bElevation || bInfoBits)
	{
    CHeightmap * pHM = GetIEditor()->GetHeightmap();
    uint32 nSizeX = pHM->m_TerrainRGBTexture.GetTileCountX();
    uint32 nSizeY = pHM->m_TerrainRGBTexture.GetTileCountY();
    uint32 * pResolutions = new uint32[nSizeX*nSizeY];
    for(int x=0; x<nSizeX; x++) for(int y=0; y<nSizeY; y++)
      pResolutions[x+y*nSizeX] = pHM->m_TerrainRGBTexture.GetTileResolution(x, y);

		GetIEditor()->Get3DEngine()->SetHeightMapMaxHeight(m_fMaxHeight);
    GetIEditor()->Get3DEngine()->GetITerrain()->SetTerrainElevation( y1, x1, w, h, m_pHeightmap, 
			terrainBlock, y1, x1, w, h,
      nSizeX ? pResolutions : NULL, nSizeX, nSizeY );

    delete [] pResolutions;
	}

	const Vec2 worldModPosition(originalInputY1 * nHeightMapUnitSize + originalInputAreaSize * nHeightMapUnitSize / 2, 
	                            originalInputX1 * nHeightMapUnitSize + originalInputAreaSize * nHeightMapUnitSize / 2);
	const float areaRadius = originalInputAreaSize * nHeightMapUnitSize / 2;
	 
	GetIEditor()->GetGameEngine()->OnTerrainModified( worldModPosition, areaRadius, (originalInputAreaSize == m_iWidth) );
}

void CHeightmap::Serialize( CXmlArchive &xmlAr,bool bSerializeVegetation,bool bUpdateTerrain )
{
	ISegmentedWorldDoc &psw = CSWManager::Get().GetDoc();

	if (xmlAr.bLoading)
	{
		// Loading
		XmlNodeRef heightmap = xmlAr.root->findChild( "Heightmap" );
		if (!heightmap)
			return;

		uint32	nWidth(m_iWidth);
		uint32	nHeight(m_iHeight);

		// To remain compatible.
		if (heightmap->getAttr( "Width",nWidth))
		{
			m_iWidth=nWidth;
		}

		// To remain compatible.
		if (heightmap->getAttr( "Height",nHeight))
		{
			m_iHeight=nHeight;
		}

		heightmap->getAttr( "WaterLevel",m_fWaterLevel );
		heightmap->getAttr( "UnitSize",m_unitSize );
		heightmap->getAttr( "MaxHeight",m_fMaxHeight );

		psw.GetSizeInUnits(m_iWidth, m_iHeight);

		int textureSize;
		if (heightmap->getAttr( "TextureSize",textureSize ))
		{
			SetSurfaceTextureSize(textureSize,textureSize);
		}

		void *pData;
		int size1,size2;

		ClearModSectors();

		if (!psw)
		{
			if (xmlAr.pNamedData->GetDataBlock( "HeightmapModSectors",pData,size1 ))
			{
				int nSize = size1 / (sizeof(int)*2);
				int * data = (int *)pData;
				for (int i=0; i<nSize; i++)
					AddModSector(data[i*2], data[i*2+1]);
				m_updateModSectors = true;
			}
		}

		// Allocate new memory
		Resize(m_iWidth, m_iHeight,m_unitSize);

		if (!psw)
		{
			// Load heightmap data.
			if (xmlAr.pNamedData->GetDataBlock( "HeightmapDataW",pData,size1 ))
			{
				float fInvPrecision = 1.0f / GetShortPrecisionScale();
				uint16 *pSrc = (uint16*)pData;
				for (int i = 0; i < m_iWidth*m_iHeight; i++)
				{
					m_pHeightmap[i] = (float)pSrc[i] * fInvPrecision;
				}
			}
			else if (xmlAr.pNamedData->GetDataBlock( "HeightmapData",pData,size1 ))
			{
				// Backward compatability for float heigthmap data.
				memcpy( m_pHeightmap,pData,size1 );
			}

			if(xmlAr.pNamedData->GetDataBlock("HeightmapLayerIdBitmap",pData,size2 ))
			{
				assert(!m_bUpdateLayerIdBitmap);

				// new version
				memcpy( m_LayerIdBitmap.GetData(), pData, min(size2,m_LayerIdBitmap.GetSize()) );
			}
			else
			{
				m_bUpdateLayerIdBitmap=true;

				// old version - to be backward compatible
				if(xmlAr.pNamedData->GetDataBlock( "HeightmapInfo",pData,size2 ))
					memcpy( m_LayerIdBitmap.GetData(), pData, min(size2,m_LayerIdBitmap.GetSize()) );
			}

			HEAP_CHECK

				// After heightmap serialization, update terrain in 3D Engine.
				if (bUpdateTerrain)
					UpdateEngineTerrain(false);

			if (m_vegetationMap && bSerializeVegetation)
			{
				m_vegetationMap->Serialize( xmlAr );
			}

		}
	}
	else
	{
		// Storing
		XmlNodeRef heightmap = xmlAr.root->newChild( "Heightmap" );

		heightmap->setAttr( "Width",(uint32)m_iWidth);
		heightmap->setAttr( "Height",(uint32)m_iHeight);
		heightmap->setAttr( "WaterLevel",m_fWaterLevel );
		heightmap->setAttr( "UnitSize",m_unitSize );
		heightmap->setAttr( "TextureSize",m_textureSize );
		heightmap->setAttr( "MaxHeight",m_fMaxHeight );

		if (!psw)
		{
			if(m_modSectors.size())
			{
				int * data = new int[m_modSectors.size()*2];
				for(int i=0; i<m_modSectors.size(); i++)
				{
					data[i*2] = m_modSectors[i].x;
					data[i*2+1] = m_modSectors[i].y;
				}
				xmlAr.pNamedData->AddDataBlock( "HeightmapModSectors",data, sizeof(int) * m_modSectors.size()*2);
				delete[] data;
			}

			// Save heightmap data as words.
			//xmlAr.pNamedData->AddDataBlock( "HeightmapData",m_pHeightmap,m_iWidth * m_iHeight * sizeof(t_hmap) );
			CWordImage hdata;
			hdata.Allocate( m_iWidth,m_iHeight );
			uint16 *pTrg = hdata.GetData();
			float fPrecisionScale = GetShortPrecisionScale();
			for (int i = 0; i < m_iWidth*m_iHeight; i++)
			{
				float val = m_pHeightmap[i];
				unsigned int h = ftoi(val*fPrecisionScale+0.5f);
				if (h > 0xFFFF) h = 0xFFFF;
				pTrg[i] = h;
			}
			xmlAr.pNamedData->AddDataBlock( "HeightmapDataW",hdata.GetData(),hdata.GetSize() );

			// old version
			// xmlAr.pNamedData->AddDataBlock( "HeightmapInfo",m_info.GetData(),m_info.GetSize() );
	
			// new version
			xmlAr.pNamedData->AddDataBlock( "HeightmapLayerIdBitmap",m_LayerIdBitmap.GetData(),m_LayerIdBitmap.GetSize() );

			if (m_vegetationMap && bSerializeVegetation)
			{
				m_vegetationMap->Serialize( xmlAr );
			}
		}
	}

	if (!psw)
		m_TerrainRGBTexture.Serialize(xmlAr);
}


void CHeightmap::ProcessAfterLoading()
{
	if(m_bUpdateLayerIdBitmap)
	{
		CLogFile::WriteLine("Converting heightmap DetailID to LayerID (new format) ...");
		CCryEditDoc *pDoc = GetIEditor()->GetDocument();

		for(uint32 dwY=0;dwY<m_iHeight;++dwY)
		for(uint32 dwX=0;dwX<m_iWidth;++dwX)
		{
			unsigned char ucVal = m_LayerIdBitmap.ValueAt(dwX,dwY);

			bool bHole = (ucVal & 0x1)!=0;
			uint32 dwDetailLayerId = (ucVal>>2) & 0xf; 

			SetHoleAt(dwX,dwY,bHole);

			uint32 dwLayerId = CTerrainManager::GetTerrainManager().GetLayerIdFromDetailLayerId(dwDetailLayerId);		assert(dwLayerId!=0xffffffff);

			if(dwLayerId!=0xffffffff)
				SetLayerIdAt(dwX,dwY,dwLayerId);
		}

		m_bUpdateLayerIdBitmap=false;
	}
}

void CHeightmap::SerializeTerrain( CXmlArchive &xmlAr, bool & )
{
  LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);

	if (xmlAr.bLoading)
	{ // Loading
		void * pData = NULL;
		int nSize = 0;

    {
      if(xmlAr.pNamedData->GetDataBlock( "TerrainCompiledData", pData, nSize ))
      {
        STerrainChunkHeader * pHeader = (STerrainChunkHeader *)pData;
        if((pHeader->nVersion == TERRAIN_CHUNK_VERSION) && (pHeader->TerrainInfo.nSectorSize_InMeters == pHeader->TerrainInfo.nUnitSize_InMeters*SECTOR_SIZE_IN_UNITS))
        {
					SSectorInfo si;
					GetSectorsInfo(si);

          ITerrain * pTerrain = GetIEditor()->Get3DEngine()->CreateTerrain(pHeader->TerrainInfo);
					// check if size of terrain in compile data match
					if (pHeader->TerrainInfo.nUnitSize_InMeters)
          if(!pTerrain->SetCompiledData((uint8*)pData,nSize,NULL,NULL))
            GetIEditor()->Get3DEngine()->DeleteTerrain();
        }
      }
    }

    if(xmlAr.pNamedData->GetDataBlock( "VoxTerrainCompiledData", pData, nSize ))
    {
      SVoxTerrainChunkHeader * pHeader = (SVoxTerrainChunkHeader *)pData;
      if(pHeader->nVersion == ISOTREE_CHUNK_VERSION || pHeader->nVersion == ISOTREE_CHUNK_VERSION_BC)
      {
        CTerrainManager::GetTerrainManager().ReloadSurfaceTypes(false);

        {
          CCryEditDoc *pDoc = GetIEditor()->GetDocument();
          for (int i = 0; i < CTerrainManager::GetTerrainManager().GetLayerCount(); i++)
          {
            CLayer *layer = CTerrainManager::GetTerrainManager().GetLayer(i);
            layer->Update3dengineInfo();
          }
        }

        {
          CWaitProgress progress( _T("Loading voxel terrain") );
          CLogFile::WriteLine("Loading voxel terrain");

          IVoxTerrain * pVoxTerrain = GetIEditor()->Get3DEngine()->CreateVoxTerrain(pHeader->TerrainInfo);
          if(pVoxTerrain && !pVoxTerrain->SetCompiledData((uint8*)pData,nSize,false,GetPlatformEndian(),NULL))
            GetIEditor()->Get3DEngine()->DeleteVoxTerrain();
          else if(pVoxTerrain && xmlAr.pNamedData->GetDataBlock( "VoxTerrainCompiledDataLayers", pData, nSize ))
            pVoxTerrain->SetLayerData(pData,nSize);
        }
      }
    }
    else 
    {
      if( ICVar * pVT = gEnv->pConsole->GetCVar("e_VoxTer") )
      {
        if( ICVar * pVTHM = gEnv->pConsole->GetCVar("e_VoxTerHeightmapEditing") )
        {
          if(pVT->GetIVal() && pVTHM->GetIVal())
          { // Init terrain engine if was not already
            gEnv->p3DEngine->DeleteVoxTerrain();

            {
              CCryEditDoc *pDoc = GetIEditor()->GetDocument();
              for (int i = 0; i < CTerrainManager::GetTerrainManager().GetLayerCount(); i++)
              {
                CLayer *layer = CTerrainManager::GetTerrainManager().GetLayer(i);
                layer->Update3dengineInfo();
              }
            }

            float fLevelSize = (float)gEnv->p3DEngine->GetTerrainSize();
            gEnv->p3DEngine->Voxel_Paint(Vec3(fLevelSize*.5f,fLevelSize*.5f,fLevelSize*.5f), fLevelSize, 
              1, Vec3(0,0,0), eveoCopyTerrainPos, evbsSphere, evetVoxelObjects, NULL, 32.f);
          }
        }
      }
    }

		GetIEditor()->GetGameEngine()->InitTerrain();
	}
	else 
	{
		if (ITerrain *pTerrain = GetIEditor()->Get3DEngine()->GetITerrain())
		{
			int nSize = pTerrain->GetCompiledDataSize();
			if (nSize > 0)
			{ // Storing
				uint8 * pData = new uint8[nSize];
				GetIEditor()->Get3DEngine()->GetITerrain()->GetCompiledData(pData,nSize,NULL,NULL,NULL,GetPlatformEndian());
				xmlAr.pNamedData->AddDataBlock( "TerrainCompiledData", pData, nSize, true );
				delete [] pData;
			}
		}

    if (IVoxTerrain *pVoxTerrain = GetIEditor()->Get3DEngine()->GetIVoxTerrain())
    {
      _smart_ptr<IMemoryBlock> pMB = pVoxTerrain->GetCompiledData(false,GetPlatformEndian(),true,NULL);
      if (pMB->GetSize() > 0)
        xmlAr.pNamedData->AddDataBlock( "VoxTerrainCompiledData", pMB->GetData(), pMB->GetSize(), false );

      int nLayerDataSize = 0;
      void * pData = 0;
      pVoxTerrain->GetLayerData(&pData,nLayerDataSize);
      if (nLayerDataSize && pData)
        xmlAr.pNamedData->AddDataBlock( "VoxTerrainCompiledDataLayers", pData, nLayerDataSize, true );
    }
  }
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::SerializeVegetation( CXmlArchive &xmlAr )
{
	if (m_vegetationMap)
	{
		m_vegetationMap->Serialize( xmlAr );
	}
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::SetWaterLevel( float waterLevel )
{
	// We modified the heightmap.
	SetModified();

	m_fWaterLevel = waterLevel;

	if(GetIEditor()->GetSystem()->GetI3DEngine())
	if(GetIEditor()->GetSystem()->GetI3DEngine()->GetITerrain())
		GetIEditor()->GetSystem()->GetI3DEngine()->GetITerrain()->SetOceanWaterLevel(waterLevel);
};

//////////////////////////////////////////////////////////////////////////
void CHeightmap::SetModified( int x1,int y1,int x2,int y2 )
{
	if(!gEnv->p3DEngine->GetITerrain())
		return;

	GetIEditor()->SetModifiedFlag();
	GetIEditor()->SetModifiedModule(eModifiedTerrain);

	AABB bounds;
	bounds.Reset();
	if (x1==0 && y1==0 && x2==0 && y2==0)
		bounds.Reset();
	else
	{
		// Here we are making sure that we will update the whole sectors where the heightmap was changed.
		int nTerrainSectorSize(gEnv->p3DEngine->GetTerrainSectorSize());

		x1*=m_unitSize;
		y1*=m_unitSize;

		x2*=m_unitSize;
		y2*=m_unitSize;

		x1/=nTerrainSectorSize;
		y1/=nTerrainSectorSize;
		x2/=nTerrainSectorSize;
		y2/=nTerrainSectorSize;

		// Y and X switched by historical reasons.
		bounds.Add(Vec3((y1-1)*nTerrainSectorSize,(x1-1)*nTerrainSectorSize,-32000.0f));
		bounds.Add(Vec3((y2+1)*nTerrainSectorSize,(x2+1)*nTerrainSectorSize,+32000.0f));
	}
	GetIEditor()->GetConsoleSync()->OnHeightmapModified( bounds );
	SW_ON_HMAP_MOD(bounds);
}

void CHeightmap::SetHoleAt( const int x, const int y, const bool bHole )
{
	if(bHole)
		m_LayerIdBitmap.ValueAt(x,y) |= HEIGHTMAP_INFO_HOLE;
	 else
		m_LayerIdBitmap.ValueAt(x,y) &= ~HEIGHTMAP_INFO_HOLE;
}

//////////////////////////////////////////////////////////////////////////
// Make hole.
void CHeightmap::MakeHole( int x1,int y1,int width,int height,bool bMake )
{
	SW_TEST_TERRAIN_MOD(x1, y1, x1 + width, y1 + height, true, true);

	if (GetIEditor()->IsUndoRecording())
		GetIEditor()->RecordUndo( new CUndoHeightmapInfo(x1,y1,width+1,height+1,this) );

	I3DEngine *engine = GetIEditor()->Get3DEngine();
	int x2 = x1 + width;
	int y2 = y1 + height;
	for (int x = x1; x <= x2; x++)
	{
		for (int y = y1; y <= y2; y++)
		{
			SetHoleAt(x,y,bMake);
		}
	}
	SW_ON_HMAP_PAINT(x1, y1, width, height, true);

	UpdateEngineTerrain( x1, y1, x2-x1, y2-y1, false, true );
}

//////////////////////////////////////////////////////////////////////////
bool CHeightmap::IsHoleAt( const int x, const int y ) const
{ 
	return (m_LayerIdBitmap.ValueAt(x,y)&HEIGHTMAP_INFO_HOLE)!=0; 
}


//////////////////////////////////////////////////////////////////////////
void CHeightmap::SetLayerIdAt( const int x, const int y, const uint32 dwLayerId )
{
	uint8 &ucRef = m_LayerIdBitmap.ValueAt(x,y);

	assert(dwLayerId<(HEIGHTMAP_INFO_SFTYPE_MASK>>HEIGHTMAP_INFO_SFTYPE_SHIFT));

	ucRef = (ucRef & (~HEIGHTMAP_INFO_SFTYPE_MASK)) | (dwLayerId<<HEIGHTMAP_INFO_SFTYPE_SHIFT);
}


//////////////////////////////////////////////////////////////////////////
uint32 CHeightmap::GetSurfaceTypeIdAtPosition( const int x, const int y ) const
{ 
	uint32 dwLayerId = (m_LayerIdBitmap.ValueAt(x,y)&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT;

	uint32 dwDetailLayerId = CTerrainManager::GetTerrainManager().GetDetailIdLayerFromLayerId(dwLayerId);

	return dwDetailLayerId & SURFACE_TYPE_MASK;
}


//////////////////////////////////////////////////////////////////////////
uint32 CHeightmap::GetLayerIdAt( const int x, const int y) const
{ 
	uint8 ucValue=0;

	if(x>=0 && y>=0 && x<m_LayerIdBitmap.GetWidth() && y<m_LayerIdBitmap.GetHeight())
		ucValue=m_LayerIdBitmap.ValueAt(x,y);

	uint32 dwLayerId = (ucValue&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT;

	return dwLayerId;
}

//////////////////////////////////////////////////////////////////////////
ColorB CHeightmap::GetColorAtPosition( const float x, const float y, bool bBilinear)
{ 
  float fLevelSize = GetIEditor()->GetSystem()->GetI3DEngine()->GetTerrainSize();

  float cx = CLAMP(x, 0, fLevelSize-1.f);
  float cy = CLAMP(y, 0, fLevelSize-1.f);

  uint32 colVal;

  if(bBilinear)
    colVal = m_TerrainRGBTexture.GetFilteredValueAt( float(cx)/fLevelSize, float(cy)/fLevelSize );
  else
    colVal = m_TerrainRGBTexture.GetValueAt( float(cx)/fLevelSize, float(cy)/fLevelSize );

  ColorB res;

  res.r = ((colVal>>16)&0xff);
  res.g = ((colVal>>8)&0xff);
  res.b = ((colVal)&0xff);
  res.a = 255;

  return res;
}

float CHeightmap::GetRGBMultiplier( ) 
{ 
  return gSettings.fBrMultiplier; 
}

//////////////////////////////////////////////////////////////////////////
float CHeightmap::GetZInterpolated( const float x, const float y )
{
	if (x <= 0 || y <= 0 || x >= m_iWidth-1 || y >= m_iHeight-1)
		return 0;

	int nX = fastftol_positive(x);
	int nY = fastftol_positive(y);

	float dx1 = x - nX;
	float dy1 = y - nY;

	float dDownLandZ0 = 
		(1.f-dx1) * (m_pHeightmap[ nX		+	nY*m_iWidth  ] ) + 
		(    dx1) * (m_pHeightmap[(nX+1)+	nY*m_iWidth  ] );

	float dDownLandZ1 = 
		(1.f-dx1) * (m_pHeightmap[ nX		+	(nY+1)*m_iWidth] ) + 
		(    dx1) * (m_pHeightmap[(nX+1)+	(nY+1)*m_iWidth] );

	float dDownLandZ = (1-dy1) * dDownLandZ0 + (  dy1) * dDownLandZ1;

	if(dDownLandZ < 0)
		dDownLandZ = 0;

	return dDownLandZ;
}

//////////////////////////////////////////////////////////////////////////
float CHeightmap::GetAccurateSlope( const float x, const float y )
{
	uint32 iHeightmapWidth = GetWidth();

	if (x <= 0 || y <= 0 || x >= m_iWidth-1 || y >= m_iHeight-1)
		return 0;

  if(I3DEngine *engine = GetIEditor()->GetSystem()->GetI3DEngine())
  {
    if(engine->GetIVoxTerrain())
    {
      Vec3 vPos(x,y,0);
      vPos *= engine->GetHeightMapUnitSize();
      Vec3 vNorm = engine->GetTerrainSurfaceNormal(vPos);
      return SATURATEB((1.f-vNorm.z)*255.f);
    }
  }

	// Calculate the slope for this point

	float arrEvelvations[8];
	int nId = 0;

	float d = 0.7f;
	arrEvelvations[nId++] = GetZInterpolated( x+1, y   );
	arrEvelvations[nId++] = GetZInterpolated( x-1, y   );
	arrEvelvations[nId++] = GetZInterpolated( x  , y+1 );
	arrEvelvations[nId++] = GetZInterpolated( x  , y-1 );
	arrEvelvations[nId++] = GetZInterpolated( x+d, y+d );
	arrEvelvations[nId++] = GetZInterpolated( x-d, y-d );
	arrEvelvations[nId++] = GetZInterpolated( x+d, y-d );
	arrEvelvations[nId++] = GetZInterpolated( x-d, y+d );

	float fMin = arrEvelvations[0];
	float fMax = arrEvelvations[0];

	for(int i=0; i<8; i++)
	{
		if(arrEvelvations[i]>fMax)
			fMax = arrEvelvations[i];
		if(arrEvelvations[i]<fMin)
			fMin = arrEvelvations[i];
	}

	// Compensate the smaller slope for bigger height fields
	return (fMax-fMin)*0.5f/GetUnitSize();
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::UpdateEngineHole( int x1,int y1,int width,int height )
{
	UpdateEngineTerrain( x1, y1, width, height, false, true );
}

//////////////////////////////////////////////////////////////////////////
CVegetationMap* CHeightmap::GetVegetationMap()
{
	return m_vegetationMap;
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::GetSectorsInfo( SSectorInfo &si )
{
	ZeroStruct(si);
	si.unitSize = m_unitSize;
	si.sectorSize = m_unitSize*SECTOR_SIZE_IN_UNITS;
	si.numSectors = m_numSectors;
	if (si.numSectors > 0)
		si.sectorTexSize = m_textureSize / si.numSectors;
	si.surfaceTextureSize = m_textureSize;
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::SetSurfaceTextureSize( int width,int height )
{
	assert( width == height );

	if (width != 0)
	{
    m_textureSize = width;
	}
	m_terrainGrid->SetResolution( m_textureSize );
}

/*
//////////////////////////////////////////////////////////////////////////
void CHeightmap::MoveBlock( const CRect &rc,CPoint offset )
{
	CRect hrc(0,0,m_iWidth,m_iHeight );
	CRect subRc = rc & hrc;
	CRect trgRc = rc;
	trgRc.OffsetRect(offset);
	trgRc &= hrc;

	if (subRc.IsRectEmpty() || trgRc.IsRectEmpty())
		return;

	if (CUndo::IsRecording())
	{
		// Must be square.
		int size = (trgRc.Width() > trgRc.Height()) ? trgRc.Width() : trgRc.Height();
		CUndo::Record( new CUndoHeightmapModify(trgRc.left,trgRc.top,size,size,this) );
	}

	CFloatImage hmap;
	CFloatImage hmapSubImage;
	hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight );
	hmapSubImage.Allocate( subRc.Width(),subRc.Height() );

	hmap.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),hmapSubImage );
	hmap.SetSubImage( trgRc.left,trgRc.top,hmapSubImage );

	CByteImage infoMap;
	CByteImage infoSubImage;
	infoMap.Attach( &m_info[0],m_iWidth,m_iHeight );
	infoSubImage.Allocate( subRc.Width(),subRc.Height() );

	infoMap.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),infoSubImage );
	infoMap.SetSubImage( trgRc.left,trgRc.top,infoSubImage );

	// Move Vegetation.
	if (m_vegetationMap)
	{
		Vec3 p1 = HmapToWorld(CPoint(subRc.left,subRc.top));
		Vec3 p2 = HmapToWorld(CPoint(subRc.right,subRc.bottom));
		Vec3 ofs = HmapToWorld(offset);
		CRect worldRC( p1.x,p1.y,p2.x,p2.y );
		// Export and import to block.
		CXmlArchive ar("Root");
		ar.bLoading = false;
		m_vegetationMap->ExportBlock( worldRC,ar );
		ar.bLoading = true;
		m_vegetationMap->ImportBlock( ar,CPoint(ofs.x,ofs.y) );
	}
}
*/

//////////////////////////////////////////////////////////////////////////
int CHeightmap::LogLayerSizes()
{
	int totalSize = 0;
	CCryEditDoc *doc = GetIEditor()->GetDocument();
	int numLayers = CTerrainManager::GetTerrainManager().GetLayerCount();
	for (int i = 0; i < numLayers; i++)
	{
		CLayer *pLayer = CTerrainManager::GetTerrainManager().GetLayer(i);
		int layerSize = pLayer->GetSize();
		totalSize += layerSize;
		CLogFile::FormatLine( "Layer %s: %dM",(const char*)pLayer->GetLayerName(),layerSize/(1024*1024) );
	}
	CLogFile::FormatLine( "Total Layers Size: %dM",totalSize/(1024*1024) );
	return totalSize;
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::ExportBlock( const CRect &inrect,CXmlArchive &xmlAr, bool bIsExportVegetation, std::set<int>* pLayerIds, std::set<int>* pSurfaceIds )
{
	// perhaps (some of) these should be parameters?
	bool bIsExportHeights = true;
	bool bIsExportLayerIDs = true;
	bool bIsExportLayersInfo = true;
	bool bIsExportRGBLayer = true;

	// Storing
	CLogFile::WriteLine("Exporting Heightmap settings...");

	XmlNodeRef heightmap = xmlAr.root->newChild( "Heightmap" );

	//CRect subRc( 0,0,m_iWidth,m_iHeight );
	//subRc &= inrect;
	CRect subRc(inrect);

	heightmap->setAttr( "Width", (uint32)m_iWidth );
	heightmap->setAttr( "Height", (uint32)m_iHeight );

	// Rectangle.
	heightmap->setAttr( "X1",subRc.left );
	heightmap->setAttr( "Y1",subRc.top );
	heightmap->setAttr( "X2",subRc.right );
	heightmap->setAttr( "Y2",subRc.bottom );

	heightmap->setAttr( "UnitSize",m_unitSize );

	if (bIsExportLayersInfo)
	{
		// detect which layers and surface types are used
		std::set<int> UsedLayerIDs;
		std::set<int> UsedSurfaceTypeIDs;
		std::set<int>* pLIds = pLayerIds ? pLayerIds : &UsedLayerIDs;
		std::set<int>* pSIds = pSurfaceIds ? pSurfaceIds : &UsedSurfaceTypeIDs;

		for (int lidY = subRc.top; lidY < subRc.bottom; ++lidY) 
		{
			for (int lidX = subRc.left; lidX < subRc.right; ++lidX) 
			{
				int lid = GetLayerIdAt(lidX, lidY);
				if (pLIds->find(lid) != pLIds->end())
					continue;
				CLayer *layer = CTerrainManager::GetTerrainManager().FindLayerByLayerId(lid);
				assert(layer);
				if (!layer)
					continue;
				pLIds->insert(lid);
				int stid = layer->GetEngineSurfaceTypeId();
				if (pSIds->find(stid) == pSIds->end())
					pSIds->insert(stid);
			}
		}

		// export used surface types
		if (!UsedSurfaceTypeIDs.empty()) 
		{
			XmlNodeRef SurfaceTypes = xmlAr.root->newChild( "SurfaceTypes" );
			for (std::set<int>::const_iterator it = UsedSurfaceTypeIDs.begin(); it != UsedSurfaceTypeIDs.end(); ++it)
			{
				int stid = *it;
				CSurfaceType *pST = CTerrainManager::GetTerrainManager().GetSurfaceTypePtr(stid);
				assert(pST);
				if (!pST)
					continue;
				CXmlArchive ar( xmlAr );
				ar.root = SurfaceTypes->newChild( "SurfaceType" );
				pST->Serialize( ar );
			}
		}

		// export used layers
		if (!UsedLayerIDs.empty())
		{
			XmlNodeRef SurfaceTypes = xmlAr.root->newChild( "TerrainLayers" );
			for (std::set<int>::const_iterator it = UsedLayerIDs.begin(); it != UsedLayerIDs.end(); ++it)
			{
				int lid = *it;
				CLayer *layer = CTerrainManager::GetTerrainManager().FindLayerByLayerId(lid);
				assert(layer);
				if (!layer)
					continue;
				CXmlArchive ar( xmlAr );
				ar.root = SurfaceTypes->newChild( "Layer" );
				layer->Serialize( ar );
			}
		}
	}

	// export heighs.
	if (bIsExportHeights)
	{
	CFloatImage hmap;
	CFloatImage hmapSubImage;
	hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight );
	hmapSubImage.Allocate( subRc.Width(),subRc.Height() );
	hmap.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),hmapSubImage );
		xmlAr.pNamedData->AddDataBlock( "HeightmapData",hmapSubImage.GetData(),hmapSubImage.GetSize() );
	}

	// export layer ids
	if (bIsExportLayerIDs)
	{
	CByteImage LayerIdBitmapImage;
	LayerIdBitmapImage.Allocate( subRc.Width(),subRc.Height() );
	m_LayerIdBitmap.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),LayerIdBitmapImage );
	xmlAr.pNamedData->AddDataBlock( "HeightmapLayerIdBitmap",LayerIdBitmapImage.GetData(),LayerIdBitmapImage.GetSize() );
	}

	// export vegetation
	if(bIsExportVegetation)
	{
		Vec3 p1 = HmapToWorld(CPoint(subRc.left,subRc.top));
		Vec3 p2 = HmapToWorld(CPoint(subRc.right,subRc.bottom));
		if (m_vegetationMap)
		{
			CRect worldRC( p1.x,p1.y,p2.x,p2.y );
			m_vegetationMap->ExportBlock( worldRC,xmlAr );
		}
	}

	// export RGB layer
	if (bIsExportRGBLayer)
	{
		float fLeft = ((float) subRc.left) / GetWidth();
		float fTop = ((float) subRc.top) / GetHeight();
		float fRight = ((float) subRc.right) / GetWidth();
		float fBottom = ((float) subRc.bottom) / GetHeight();
		uint32 nRes = m_TerrainRGBTexture.CalcMaxLocalResolution(fLeft, fTop, fRight, fBottom);
		int w = (subRc.right-subRc.left)*nRes/GetWidth();
		int h = (subRc.bottom-subRc.top)*nRes/GetHeight();
		XmlNodeRef RGBLayer = xmlAr.root->newChild( "RGBLayer" );
		RGBLayer->setAttr("width", w);
		RGBLayer->setAttr("height", h);
		CImage image;
  		image.Allocate(w, h);
		m_TerrainRGBTexture.GetSubImageStretched(fLeft, fTop, fRight, fBottom, image);
		xmlAr.pNamedData->AddDataBlock( "RGBLayer",image.GetData(),image.GetSize() );
	}
}

float SampleImage (const CFloatImage* pImage, float x, float y)
{
	float w = (float)pImage->GetWidth();
	float h = (float)pImage->GetHeight();
	float px = (w - 1) * x;
	float py = (h - 1) * y;
	float tx = px - floorf(px);
	float ty = py - floorf(py);
	int x1 = clamp_tpl<int>((int)px, 0, w - 1);
	int x2 = clamp_tpl<int>(x1 + 1,  0, w - 1);
	int y1 = clamp_tpl<int>((int)py, 0, h - 1);
	int y2 = clamp_tpl<int>(y1 + 1,  0, h - 1);
	float v1 = pImage->ValueAt(x1, y1);
	float v2 = pImage->ValueAt(x2, y1);
	float v3 = pImage->ValueAt(x1, y2);
	float v4 = pImage->ValueAt(x2, y2);
	return (v1 * (1 - tx) + v2 * tx) * (1 - ty) + (v3 * (1 - tx) + v2 * tx) * ty;
}

unsigned char SampleImage (const CByteImage* pImage, float x, float y)
{
	int w = pImage->GetWidth();
	int h = pImage->GetHeight();
	int ix = clamp_tpl<int>((int)((w - 1) * x + 0.5f), 0, w - 1);
	int iy = clamp_tpl<int>((int)((h - 1) * y + 0.5f), 0, h - 1);
	return pImage->ValueAt(ix, iy);
}

ColorB BilinearColorInterpolation (const ColorB& c11, const ColorB& c12, const ColorB& c21, const ColorB& c22, float tx, float ty)
{
	uint8 r = (uint8)((c11.r * (1 - tx) + c12.r * tx) * (1 - ty) + (c21.r * (1 - tx) + c22.r * tx) * ty);
	uint8 g = (uint8)((c11.g * (1 - tx) + c12.g * tx) * (1 - ty) + (c21.g * (1 - tx) + c22.g * tx) * ty);
	uint8 b = (uint8)((c11.b * (1 - tx) + c12.b * tx) * (1 - ty) + (c21.b * (1 - tx) + c22.b * tx) * ty);
	uint8 a = (uint8)((c11.a * (1 - tx) + c12.a * tx) * (1 - ty) + (c21.a * (1 - tx) + c22.a * tx) * ty);
	return ColorB(r, g, b, a);
}

unsigned int SampleImage (const CImage* pImage, float x, float y)
{
	float w = (float)pImage->GetWidth();
	float h = (float)pImage->GetHeight();
	float px = (w - 1) * x;
	float py = (h - 1) * y;
	float tx = px - floorf(px);
	float ty = py - floorf(py);
	int x1 = clamp_tpl<int>((int)px, 0, w - 1);
	int x2 = clamp_tpl<int>(x1 + 1,  0, w - 1);
	int y1 = clamp_tpl<int>((int)py, 0, h - 1);
	int y2 = clamp_tpl<int>(y1 + 1,  0, h - 1);
	ColorB v1 = pImage->ValueAt(x1, y1);
	ColorB v2 = pImage->ValueAt(x2, y1);
	ColorB v3 = pImage->ValueAt(x1, y2);
	ColorB v4 = pImage->ValueAt(x2, y2);

	return BilinearColorInterpolation(v1, v2, v3, v4, tx, ty).pack_abgr8888();
}

CRect GetBounds (const Matrix34& mat, int w, int h)
{
	Vec3 p0 = mat.TransformPoint(Vec3(0, 0, 0));
	Vec3 p1 = mat.TransformPoint(Vec3(1, 0, 0));
	Vec3 p2 = mat.TransformPoint(Vec3(0, 1, 0));
	Vec3 p3 = mat.TransformPoint(Vec3(1, 1, 0));
	CRect rect;
	rect.left   = clamp_tpl<int>((int)floorf(min(min(p0.x, p1.x), min(p2.x, p3.x))),     0, w);
	rect.right  = clamp_tpl<int>((int) ceilf(max(max(p0.x, p1.x), max(p2.x, p3.x))) + 1, 0, h);
	rect.top    = clamp_tpl<int>((int)floorf(min(min(p0.y, p1.y), min(p2.y, p3.y))),     0, w);
	rect.bottom = clamp_tpl<int>((int) ceilf(max(max(p0.y, p1.y), max(p2.y, p3.y))) + 1, 0, h);
	return rect;
}

template <typename T>
void BlitImage (TImage<T>* pDest, TImage<T>* pSrc, const Matrix34& mat, T delta)
{
	Matrix34 invMat = mat.GetInverted();
	CRect bounds = GetBounds(mat, pDest->GetWidth(), pDest->GetHeight());
	float minX =       -1.0f / (2.0f * (pSrc->GetWidth()  - 1));
	float minY =       -1.0f / (2.0f * (pSrc->GetHeight() - 1));
	float maxX = 1.0f + 1.0f / (2.0f * (pSrc->GetWidth()  - 1));
	float maxY = 1.0f + 1.0f / (2.0f * (pSrc->GetHeight() - 1));
	for (int y = bounds.top; y < bounds.bottom; ++y)
	{
		for (int x = bounds.left; x < bounds.right; ++x)
		{
			Vec3 uvw = invMat.TransformPoint(Vec3((float)x, (float)y, 0));
			if (uvw.x < minX || uvw.y < minY || uvw.x > maxX || uvw.y > maxY)
				continue;
			pDest->ValueAt(x, y) = delta + SampleImage(pSrc, uvw.x, uvw.y);
		}
	}
}

void CHeightmap::PasteHeightmapPrecise (CFloatImage* pHeights, CRect rc, float heightDelta)
{
	CFloatImage hmap;
	hmap.Attach(m_pHeightmap, m_iWidth, m_iHeight);
	CryLogAlways("pHeights (%d , %d) --- rc (%d, %d)", pHeights->GetWidth(), pHeights->GetHeight(), rc.Width(), rc.Height());
	for (int y = rc.top; y < rc.bottom; ++y)
		for (int x = rc.left; x < rc.right; ++x)
			if (x >= 0 && y >= 0 && x < m_iWidth && y < m_iHeight)
				hmap.ValueAt(x, y) = pHeights->ValueAt(x - rc.left, y - rc.top);
	int size = max(rc.Width(), rc.Height());
	UpdateEngineTerrain(rc.left, rc.top, size, size, true, false);

	AABB box;
	CSWManager::CoordToAABB(box, rc.left, rc.top, rc.right, rc.bottom, true, false);
	SW_ON_HMAP_MOD(box);
}

void CHeightmap::PasteHeightmap (CFloatImage* pHeights, const Matrix34& transform, float heightDelta)
	{
	CFloatImage hmap;
	hmap.Attach(m_pHeightmap, m_iWidth, m_iHeight);
	CRect bounds = GetBounds(transform, m_iWidth, m_iHeight);
	BlitImage(&hmap, pHeights, transform, heightDelta);
	int size = max(bounds.Width(), bounds.Height());
	UpdateEngineTerrain(bounds.left, bounds.top, size, size, true, false);

	AABB box;
	CSWManager::CoordToAABB(box, bounds.left, bounds.top, bounds.right, bounds.bottom, true, false);
	SW_ON_HMAP_MOD(box);
	}

void CHeightmap::PasteLayerIds (CByteImage* pIds, const Matrix34& transform)
{
	CRect bounds = GetBounds(transform, m_iWidth, m_iHeight);
	BlitImage(&m_LayerIdBitmap, pIds, transform, (uint8)0);
	int size = max(bounds.Width(), bounds.Height());
	UpdateEngineTerrain(bounds.left, bounds.top, size, size, true, false);

	//Segmented World Notification
	SW_ON_HMAP_PAINT(bounds.left, bounds.top, bounds.Width(), bounds.Height(), true);
}
	
//////////////////////////////////////////////////////////////////////////
CPoint CHeightmap::ImportBlock( CXmlArchive &xmlAr,CPoint newPos,bool bUseNewPos, float heightOffset, bool bOnlyVegetation, int nRot)
{
	bool bImportHeights = !bOnlyVegetation;
	bool bImportLayerIDs = !bOnlyVegetation;
	bool bImportLayersInfo = !bOnlyVegetation;
	bool bImportRGBLayer = !bOnlyVegetation;

	CLogFile::WriteLine("Importing Heightmap settings...");

	XmlNodeRef heightmap = xmlAr.root->findChild( "Heightmap" );
	if (!heightmap)
		return CPoint(0,0);

	CPoint offset(0,0);
	CRect subRc;
	int srcWidth, srcHeight;
	if(nRot==1 || nRot==3)
	{
		heightmap->getAttr( "Y1",subRc.left );
		heightmap->getAttr( "X1",subRc.top );
		heightmap->getAttr( "Y2",subRc.right );
		heightmap->getAttr( "X2",subRc.bottom );
		srcWidth = subRc.bottom - subRc.top;
		srcHeight = subRc.right - subRc.left;
	}
	else
	{
		heightmap->getAttr( "X1",subRc.left );
		heightmap->getAttr( "Y1",subRc.top );
		heightmap->getAttr( "X2",subRc.right );
		heightmap->getAttr( "Y2",subRc.bottom );
		srcWidth = subRc.right - subRc.left;
		srcHeight = subRc.bottom - subRc.top;
	}
	
	if (bUseNewPos)
	{
		offset = CPoint( newPos.x-subRc.left,newPos.y-subRc.top );
		subRc.OffsetRect( offset );
	}

	if (GetIEditor()->IsUndoRecording())
	{
		GetIEditor()->RecordUndo( new CUndoHeightmapModify(subRc.left, subRc.top, subRc.Width(), subRc.Height(), this) );
		GetIEditor()->RecordUndo( new CUndoHeightmapInfo(subRc.left, subRc.top, subRc.Width(), subRc.Height(), this) );
	}


		void *pData;
		int size;

	// import heights
	if (bImportHeights && xmlAr.pNamedData->GetDataBlock( "HeightmapData",pData,size ))
		{
			// Backward compatability for float heigthmap data.
			CFloatImage hmap;
			CFloatImage hmapSubImage;
			hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight );
			hmapSubImage.Attach( (float*)pData, srcWidth, srcHeight );

			if(nRot)
			{
				CFloatImage hmapSubImageRot;
				hmapSubImageRot.RotateOrt(hmapSubImage, nRot);
				hmap.SetSubImage( subRc.left,subRc.top, hmapSubImageRot, heightOffset, m_fMaxHeight );
			}
			else
				hmap.SetSubImage( subRc.left,subRc.top,hmapSubImage, heightOffset, m_fMaxHeight );
		}

	// import terrain layers and layer ids
	if (bImportLayerIDs && xmlAr.pNamedData->GetDataBlock( "HeightmapLayerIdBitmap",pData,size ))
		{
			CByteImage LayerIdBitmapImage;
			LayerIdBitmapImage.Attach( (unsigned char*)pData, srcWidth, srcHeight );

		if (bImportLayersInfo)
		{
			// load surface types (ignore if the same name already exists)
			XmlNodeRef SurfaceTypes = xmlAr.root->findChild( "SurfaceTypes" );
			if (SurfaceTypes) 
			{
				for (int i = 0; i < SurfaceTypes->getChildCount(); i++)
				{
					CXmlArchive ar( xmlAr );
					ar.root = SurfaceTypes->getChild(i);
					CString Name;
					if (!ar.root->getAttr( "Name",Name ))
						continue;
					int iST = CTerrainManager::GetTerrainManager().FindSurfaceType(Name);
					if (iST >= 0)
						continue;
					CSurfaceType *pST = new CSurfaceType;
					pST->Serialize(ar);
					CTerrainManager::GetTerrainManager().AddSurfaceType(pST);
				}
				CTerrainManager::GetTerrainManager().ConsolidateSurfaceTypes();
				CTerrainManager::GetTerrainManager().ReloadSurfaceTypes(false, false);
			}

			// load terrain layers, remap layer IDs if necessary
			XmlNodeRef TerrainLayers = xmlAr.root->findChild( "TerrainLayers" );
			if (TerrainLayers) 
			{
				std::map<int,int> LayerIDMap;
				bool bNeedsRemap = false;

				for (int i = 0; i < TerrainLayers->getChildCount(); i++)
				{
					CXmlArchive ar( xmlAr );
					ar.root = TerrainLayers->getChild(i);
					CString Name;
					if (!ar.root->getAttr( "Name",Name ))
						continue;
					int lidOld;
					if (!ar.root->getAttr( "LayerId",lidOld ))
						continue;
					CLayer *layer = CTerrainManager::GetTerrainManager().FindLayer(Name);
					if (!layer) 
					{
						layer = new CLayer;
						ar.root->setAttr( "LayerId",0xffffffff );
						layer->Serialize(ar);
						CTerrainManager::GetTerrainManager().AddLayer(layer);
					}
					int lidNew = layer->GetOrRequestLayerId();
					if (lidOld == lidNew)
						continue;
					bNeedsRemap = true;
					LayerIDMap[lidOld] = lidNew;
				}

				// remap ids if needed
				if (bNeedsRemap)
				{
					for (int y = 0; y < LayerIdBitmapImage.GetHeight(); ++y)
					{
						for (int x = 0; x < LayerIdBitmapImage.GetWidth(); ++x)
						{
							byte &rValue = LayerIdBitmapImage.ValueAt(x, y);
							int lidOld =(rValue&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT;
							std::map<int,int>::const_iterator itNew = LayerIDMap.find(lidOld);
							if (itNew == LayerIDMap.end())
								continue;
							int lidNew = itNew->second;
							rValue = (rValue & ~HEIGHTMAP_INFO_SFTYPE_MASK) | (lidNew << HEIGHTMAP_INFO_SFTYPE_SHIFT);
						}
					}
				}
			}
		} //end of: if (bImportLayersInfo)

			if(nRot)
			{
				CByteImage LayerIdBitmapImageRot;
				LayerIdBitmapImageRot.RotateOrt(LayerIdBitmapImage, nRot);
			m_LayerIdBitmap.SetSubImage( subRc.left,subRc.top,LayerIdBitmapImageRot, m_fMaxHeight );
			}
			else
			m_LayerIdBitmap.SetSubImage( subRc.left,subRc.top,LayerIdBitmapImage, m_fMaxHeight );
	} // end of: import terrain layers and layer ids

		// After heightmap serialization, update terrain in 3D Engine.
	if (bImportHeights || bImportLayerIDs)
	{
		int wid = subRc.Width()+2;
		if(wid<subRc.Height()+2)
			wid=subRc.Height()+2;
		UpdateEngineTerrain( subRc.left-1,subRc.top-1,wid,wid,true,true );
	}

	// import RGB layer
	if (bImportRGBLayer && xmlAr.pNamedData->GetDataBlock( "RGBLayer",pData,size ))
	{
		XmlNodeRef RGBLayer = xmlAr.root->findChild( "RGBLayer" );
		int w, h;
		if (RGBLayer && RGBLayer->getAttr("width", w) && RGBLayer->getAttr("height", h))
		{
			float fLeft = ((float) subRc.left) / GetWidth();
			float fTop = ((float) subRc.top) / GetHeight();
			float fRight = ((float) subRc.right) / GetWidth();
			float fBottom = ((float) subRc.bottom) / GetHeight();
			CImage image;
			image.Attach( (uint32*)pData, w, h );
			if (nRot)
			{
				CImage imgRot;
				image.RotateOrt(imgRot, nRot);
				m_TerrainRGBTexture.SetSubImageStretched(fLeft, fTop, fRight, fBottom, imgRot);
			}
			else
				m_TerrainRGBTexture.SetSubImageStretched(fLeft, fTop, fRight, fBottom, image);
			UpdateLayerTexture(subRc);
		}
	}


	// import vegetation
	if (m_vegetationMap)
	{
		Vec3 ofs = HmapToWorld(offset);
		//m_vegetationMap->ImportBlock( xmlAr,CPoint(ofs.x,ofs.y) );
	}

	return offset;
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::RemoveLayerID(uint32 layerID)
{
	if (CSWManager::Get().IsSWDoc())
	{
		for (int y = 0; y < m_LayerIdBitmap.GetHeight(); ++y)
		{
			for (int x = 0; x < m_LayerIdBitmap.GetWidth(); ++x)
			{
				byte &rValue = m_LayerIdBitmap.ValueAt(x, y);
				int lidOld =(rValue&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT;
				
				if (lidOld != layerID) {
					continue;
	}

				int lidNew = 0;
				rValue = (rValue & ~HEIGHTMAP_INFO_SFTYPE_MASK) | (lidNew << HEIGHTMAP_INFO_SFTYPE_SHIFT);
			}
		}
	}
}


//////////////////////////////////////////////////////////////////////////
void CHeightmap::ClearSegmentHeights(const CRect &rc)
	{
	int w = rc.Width() * sizeof(t_hmap);
	for (int y = rc.top; y < rc.bottom; ++y)
		memset(m_pHeightmap + y * m_iWidth + rc.left, 0, w);
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::ImportSegmentHeights(CMemoryBlock &mem, const CRect &rc)
		{
	CFloatImage hmapSubImage;
	hmapSubImage.Attach((float*)mem.GetBuffer(), rc.Width(), rc.Height());

	CFloatImage hmap;
	hmap.Attach(m_pHeightmap, m_iWidth, m_iHeight );

	hmap.SetSubImage(rc.left, rc.top, hmapSubImage);
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::ExportSegmentHeights(CMemoryBlock &mem, const CRect &rc)
{
	CFloatImage hmap;
	hmap.Attach(m_pHeightmap, m_iWidth, m_iHeight);

	CFloatImage hmapSubImage;
	hmap.GetSubImage(rc.left, rc.top, rc.Width(), rc.Height(), hmapSubImage);

	hmapSubImage.Compress(mem);
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::ClearSegmentLayerIDs(const CRect &rc)
{
	int w = rc.Width(); // * sizeof(byte)
	byte *pLayerIDs = m_LayerIdBitmap.GetData();
	for (int y = rc.top; y < rc.bottom; ++y)
		memset(pLayerIDs + y * m_iWidth + rc.left, 0, w);
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::ImportSegmentLayerIDs(CMemoryBlock &mem, const CRect &rc)
{
//TODO: load layers info, update terrain layers, remap indicies
	typedef std::map<uint32,GUID> TUIntGuidMap;
	typedef std::pair<uint32,GUID> TUIntGuidPair;

	typedef std::map<uint32,uint32> TUIntUIntMap;
	typedef std::pair<uint32,uint32> TUIntUIntPair;
	

	int nImageMemSize = sizeof(unsigned char)*rc.Width()*rc.Height();
	bool isLayerIDsInfoStored = (mem.GetSize() != nImageMemSize);
	
	unsigned char *p = (unsigned char*)mem.GetBuffer();
	int curr = 0;
	
	TUIntUIntMap tLayerIDMap;

	bool isNeedToReindex = false;

	if (isLayerIDsInfoStored) {

		TUIntGuidMap tMap;

		int nSavedLayersCount;
		memcpy(&nSavedLayersCount, &p[curr], sizeof(int));
		curr += sizeof(int); // layers count

		for (int i = 0; i < nSavedLayersCount; i++)
		{
			TUIntGuidPair tPair;
			memcpy(&tPair.first, &p[curr], sizeof(uint32));
			curr += sizeof(uint32); //layer id

			memcpy(&tPair.second, &p[curr], sizeof(GUID));
			curr += sizeof(GUID); // layer GUID

			tMap.insert(tPair);
	}

		int nLayersCount = CTerrainManager::GetTerrainManager().GetLayerCount();
		if (nLayersCount != tMap.size())
			isNeedToReindex = true;

		TUIntGuidMap::const_iterator it;
		for (it = tMap.begin(); it != tMap.end(); ++it) {
			TUIntUIntPair tUIntPair;
			tUIntPair.first = it->first;
			tUIntPair.second = 0;
			bool bFound = false;
			for (int i = 0; i < nLayersCount; i++)
	{
				CLayer* pLayer = CTerrainManager::GetTerrainManager().GetLayer(i);
				
				if (it->second == pLayer->GetGUID())
		{
					tUIntPair.second = pLayer->GetCurrentLayerId();
					if (tUIntPair.first != tUIntPair.second)
						isNeedToReindex = true;
					bFound = true;
					//tLayerIDMap.insert(tUIntPair);
					break;
				}
			}
			
			if(!bFound)
				isNeedToReindex = true;

			tLayerIDMap.insert(tUIntPair);
		}
	}

	CRY_ASSERT((curr + nImageMemSize)== mem.GetSize());
	
	CMemoryBlock memImgData;
	memImgData.Allocate(nImageMemSize);
	memcpy(memImgData.GetBuffer(), &p[curr], nImageMemSize);

	CByteImage LayerIdBitmapImage;
	LayerIdBitmapImage.Attach((unsigned char*) memImgData.GetBuffer(), rc.Width(), rc.Height());

	if (isLayerIDsInfoStored) {

		// remap ids if needed
		if (isNeedToReindex)
		{
			for (int y = 0; y < LayerIdBitmapImage.GetHeight(); ++y)
			{
				for (int x = 0; x < LayerIdBitmapImage.GetWidth(); ++x)
			{
					byte &rValue = LayerIdBitmapImage.ValueAt(x, y);
					int lidOld =(rValue&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT;
					TUIntUIntMap::const_iterator itNew = tLayerIDMap.find(lidOld);
					if (itNew == tLayerIDMap.end()) {
					continue;
					}
					int lidNew = itNew->second;
					rValue = (rValue & ~HEIGHTMAP_INFO_SFTYPE_MASK) | (lidNew << HEIGHTMAP_INFO_SFTYPE_SHIFT);
				}
			}
		}
	}
			
	m_LayerIdBitmap.SetSubImage(rc.left, rc.top, LayerIdBitmapImage );
			}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::ExportSegmentLayerIDs(CMemoryBlock &mem, const CRect &rc)
{
	//TODO: save layers info with number of "instances" per layer
	
	int sizeOfMem = 0;
	typedef std::map<uint32,GUID> TUIntGuidMap;
	typedef std::pair<uint32,GUID> TUIntGuidPair;
	
	TUIntGuidMap tMap;
	int nLayersCount = CTerrainManager::GetTerrainManager().GetLayerCount();
	sizeOfMem += sizeof(int); // num layers

	for (int i = 0; i < nLayersCount; i++)
	{
		CLayer* pLayer = CTerrainManager::GetTerrainManager().GetLayer(i);
		TUIntGuidPair tPair;
		tPair.first = pLayer->GetCurrentLayerId();
		sizeOfMem += sizeof(uint32); // layer id

		tPair.second = pLayer->GetGUID();
		
		sizeOfMem += sizeof(GUID); // layer GUID
		
		tMap.insert(tPair);
		}

	//size of image
	sizeOfMem += (sizeof(unsigned char)*rc.Width()*rc.Height());


	CMemoryBlock memAggregated;
	memAggregated.Allocate(sizeOfMem);

	unsigned char *p = (unsigned char*)memAggregated.GetBuffer();

	int curr = 0;
	
	// saving of layer info
	//-----------------------------
	
	memcpy(&p[curr], &nLayersCount, sizeof(int));
	curr += sizeof(int); // layers count
	
	TUIntGuidMap::iterator it;

	for (it = tMap.begin(); it != tMap.end(); ++it) {
		memcpy(&p[curr], &it->first, sizeof(uint32));
		curr += sizeof(uint32); //layer id

		memcpy(&p[curr], &it->second, sizeof(GUID));
		curr += sizeof(GUID); // layer GUID
	}

	
	CMemoryBlock memImgData;
	CMemoryBlock tempMem;

	CByteImage LayerIdBitmapImage;
	m_LayerIdBitmap.GetSubImage(rc.left, rc.top, rc.Width(), rc.Height(), LayerIdBitmapImage);
	LayerIdBitmapImage.Compress(tempMem);
	tempMem.Uncompress(memImgData);

	// saving LayerIds
	memcpy(&p[curr], memImgData.GetBuffer(), memImgData.GetSize());

	memAggregated.Compress(mem);
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::CopyFrom( t_hmap *pHmap,unsigned char *pLayerIdBitmap,int resolution )
{
	int x,y;
	int res = min(resolution,(int)m_iWidth);
	for (y = 0; y < res; y++)
	{
		for (x = 0; x < res; x++)
		{
			SetXY( x,y, pHmap[x + y*resolution] );
			m_LayerIdBitmap.ValueAt( x,y ) = pLayerIdBitmap[x + y*resolution];
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::CopyFromInterpolate( t_hmap *pHmap,unsigned char *pLayerIdBitmap,int resolution, float fKof )
{
	if(fKof>1)
	{
		int kof = (int) fKof;
		for (int y=0, y2=0; y < m_iHeight; y+=kof, y2++)
			for (int x=0, x2=0; x < m_iWidth; x+=kof, x2++)
			{
				for(int y1=0; y1<kof; y1++)
					for(int x1=0; x1<kof; x1++)
					{
						if(x + x1<m_iWidth && y + y1<m_iHeight && x2<resolution && y2<resolution)
						{
							float kofx = (float) x1/kof;
							float kofy = (float) y1/kof;

							int x3 = x2+1;
							int y3 = y2+1;
	/*
							if(x2>0 && y2>0 && x3+1<resolution && y3+1<resolution)
							{
								t_hmap p1xy2 = pHmap[x2 + y2*resolution] + (pHmap[x2 + y2*resolution] - pHmap[x2-1 + y2*resolution])/3;
								t_hmap p2xy2 = pHmap[x3 + y2*resolution] + (pHmap[x3 + y2*resolution] - pHmap[x3+1 + y2*resolution])/3;

								t_hmap p1xy3 = pHmap[x2 + y3*resolution] + (pHmap[x2 + y3*resolution] - pHmap[x2-1 + y3*resolution])/3;
								t_hmap p2xy3 = pHmap[x3 + y3*resolution] + (pHmap[x3 + y3*resolution] - pHmap[x3+1 + y3*resolution])/3;


								t_hmap pxy2 = p1xy2 
							}
							else
							{
	*/
								if(x3>=resolution)
									x3 = x2;
								if(y3>=resolution)
									y3 = y2;

								SetXY( x + x1,y + y1, 
								(1.0f-kofy) * ((1.0f-kofx)*pHmap[x2 + y2*resolution] + kofx*pHmap[x3 + y2*resolution])+
								kofy *((1.0f-kofx)*pHmap[x2 + y3*resolution] + kofx*pHmap[x3 + y3*resolution])
								);
	//						}

							m_LayerIdBitmap.ValueAt( x + x1,y + y1 ) = pLayerIdBitmap[x2 + y2*resolution];
						}
					}
				unsigned char val = pLayerIdBitmap[x2 + y2*resolution];

				if(y2<resolution-1)
				{
					unsigned char val1 = pLayerIdBitmap[x2 + (y2+1)*resolution];
					if(val1>val)
						m_LayerIdBitmap.ValueAt( x,y+kof-1 ) = val1;
				}

				if(x2<resolution-1)
				{
					unsigned char val1 = pLayerIdBitmap[x2+1 + y2*resolution];
					if(val1>val)
						m_LayerIdBitmap.ValueAt( x+kof-1, y ) = val1;
				}

				if(x2<resolution-1 && y2<resolution-1)
				{
					// choose max occured value or max value between several max occured.
					unsigned char valu[4];
					int bal[4];
					valu[0] = val;
					valu[1] = pLayerIdBitmap[x2+1 + y2*resolution];
					valu[2] = pLayerIdBitmap[x2 + (y2+1)*resolution];
					valu[3] = pLayerIdBitmap[x2+1 + (y2+1)*resolution];

					int max=0;

					int k=0;
					for(k=0; k<4; k++)
					{
						bal[k]=1000+valu[k];
						if(bal[k]>max)
						{
							val = valu[k];
							max=bal[k];
						}
						if(k>0)
						{
							for(int kj=0; kj<k; kj++)
							{
								if(valu[kj]==valu[k])
								{
									valu[k] = 0;
									bal[kj]+=bal[k];
									bal[k] = 0;
									if(bal[kj]>max)
									{
										val = valu[kj];
										max=bal[kj];
									}
									break;
								}
							}
						}
					}
					
					m_LayerIdBitmap.ValueAt( x+kof-1, y+kof-1 ) = val;
				}
			}
	}
	else if(0.1f<fKof && fKof<1.0f)
	{
		int kof = int(1.0f / fKof + 0.5f);
		for (int y=0; y < m_iHeight; y++)
			for (int x=0; x < m_iWidth; x++)
			{
				int x2 = x*kof;
				int y2 = y*kof;
				if(x2<resolution && y2<resolution)
				{
					SetXY( x,y, pHmap[x2 + y2*resolution]);
					m_LayerIdBitmap.ValueAt( x, y ) = pLayerIdBitmap[x2 + y2*resolution];
				}
			}
	}
}

//////////////////////////////////////////////////////////////////////////
void CHeightmap::InitSectorGrid()
{
	SSectorInfo si;
	GetSectorsInfo(si);
	GetTerrainGrid()->InitSectorGrid( si.numSectors );
}



void CHeightmap::GetMemoryUsage( ICrySizer *pSizer )
{
	pSizer->Add(*this);

	if(m_pHeightmap)
	{
		SIZER_COMPONENT_NAME(pSizer,"Heightmap 2D Array");
		pSizer->Add(m_pHeightmap,m_iWidth*m_iHeight);
	}

	if(m_pNoise)
		m_pNoise->GetMemoryUsage(pSizer);

	{
		SIZER_COMPONENT_NAME(pSizer,"LayerId 2D Array");
		pSizer->Add((char *)m_LayerIdBitmap.GetData(),m_LayerIdBitmap.GetSize());
	}

	if(m_vegetationMap)
		m_vegetationMap->GetMemoryUsage(pSizer);

	if(m_terrainGrid)
		m_terrainGrid->GetMemoryUsage(pSizer);

	{
		SIZER_COMPONENT_NAME(pSizer,"RGBLayer");

		m_TerrainRGBTexture.GetMemoryUsage(pSizer);
	}
}


t_hmap CHeightmap::GetSafeXY( const uint32 dwX, const uint32 dwY ) const
{
	if(dwX<=0 || dwY<=0 || dwX>=(uint32)m_iWidth || dwY>=(uint32)m_iHeight)
		return 0;

	return m_pHeightmap[dwX + dwY*m_iWidth]; 
}

void CHeightmap::RecordUndo( int x1,int y1,int width,int height, bool bInfo)
{
	if (GetIEditor()->IsUndoRecording())
	{
		if (bInfo)
			GetIEditor()->RecordUndo( new CUndoHeightmapInfo( x1, y1, width, height,this) );
		else
		GetIEditor()->RecordUndo( new CUndoHeightmapModify( x1, y1, width, height,this) );
}
}

void CHeightmap::UpdateLayerTexture( const CRect &rect )
{
	I3DEngine *p3DEngine = GetIEditor()->Get3DEngine();
  bool bSW = p3DEngine->IsSegmentedWorldActive();
	if(!bSW && p3DEngine->GetTerrainTextureMultiplier()==0.0f)
		return; // texture is not generated

  float fBrMultiplier = bSW ? 1.0f : 1.f/p3DEngine->GetTerrainTextureMultiplier();

	float x1 = HmapToWorld(CPoint(rect.left, rect.top)).x;
	float y1 = HmapToWorld(CPoint(rect.left, rect.top)).y;
	float x2 = HmapToWorld(CPoint(rect.right, rect.bottom)).x;
	float y2 = HmapToWorld(CPoint(rect.right, rect.bottom)).y;

	int iTexSectorSize = GetIEditor()->Get3DEngine()->GetTerrainTextureNodeSizeMeters();
	int iTerrainSize = GetIEditor()->Get3DEngine()->GetTerrainSize();
	int iTexSectorsNum = iTerrainSize /iTexSectorSize;

	uint32 dwFullResolution = m_TerrainRGBTexture.CalcMaxLocalResolution((float)rect.left/GetWidth(), (float)rect.top/GetHeight(), (float)rect.right/GetWidth(), (float)rect.bottom/GetHeight());
	uint32 dwNeededResolution = dwFullResolution/iTexSectorsNum;

	int iSectMinX = (int)floor(y1/iTexSectorSize);
	int iSectMinY = (int)floor(x1/iTexSectorSize);
	int iSectMaxX = (int)floor(y2/iTexSectorSize);
	int iSectMaxY = (int)floor(x2/iTexSectorSize);

	CWaitProgress progress( "Updating Terrain Layers" );

	int nTotalSectors = (iSectMaxX-iSectMinX+1) * (iSectMaxY-iSectMinY+1);
	int nCurrSectorNum = 0;
	for(int iSectX=iSectMinX; iSectX<=iSectMaxX; iSectX++)
		for(int iSectY=iSectMinY; iSectY<=iSectMaxY; iSectY++)
		{
			progress.Step(  (100*nCurrSectorNum)/nTotalSectors );
			nCurrSectorNum++;
			int iLocalOutMinX = y1*dwFullResolution/iTerrainSize - iSectX*dwNeededResolution;
			int iLocalOutMinY = x1*dwFullResolution/iTerrainSize - iSectY*dwNeededResolution;
			int iLocalOutMaxX = y2*dwFullResolution/iTerrainSize - iSectX*dwNeededResolution;
			int iLocalOutMaxY = x2*dwFullResolution/iTerrainSize - iSectY*dwNeededResolution;

			if(iLocalOutMinX < 0) iLocalOutMinX = 0;
			if(iLocalOutMinY < 0) iLocalOutMinY = 0;
			if(iLocalOutMaxX > dwNeededResolution) iLocalOutMaxX = dwNeededResolution;
			if(iLocalOutMaxY > dwNeededResolution) iLocalOutMaxY = dwNeededResolution;

			if(iLocalOutMinX!=iLocalOutMaxX && iLocalOutMinY!=iLocalOutMaxY)
			{
				bool bFullRefreshRequired = GetTerrainGrid()->GetSector(CPoint(iSectX, iSectY))->textureId==0;
				bool bRecreated;
				int texId = GetTerrainGrid()->LockSectorTexture( CPoint(iSectX, iSectY),dwFullResolution/iTexSectorsNum,bRecreated);
				if(bFullRefreshRequired || bRecreated)
				{
					iLocalOutMinX = 0;
					iLocalOutMinY = 0;
					iLocalOutMaxX = dwNeededResolution;
					iLocalOutMaxY = dwNeededResolution;
				}

				CImage image;
				image.Allocate(iLocalOutMaxX-iLocalOutMinX,iLocalOutMaxY-iLocalOutMinY);

				m_TerrainRGBTexture.GetSubImageStretched(
					(iSectX+(float)iLocalOutMinX/dwNeededResolution)/iTexSectorsNum,
					(iSectY+(float)iLocalOutMinY/dwNeededResolution)/iTexSectorsNum,
					(iSectX+(float)iLocalOutMaxX/dwNeededResolution)/iTexSectorsNum,
					(iSectY+(float)iLocalOutMaxY/dwNeededResolution)/iTexSectorsNum,
					image);

				// convert RGB colour into format that has less compression artefacts for brightness variations
				{
					bool bDX10 = GetIEditor()->GetRenderer()->GetRenderType()==eRT_DX11;			// i would be cleaner if renderer would ensure the behave the same but that can be slower 

					uint32 dwWidth = image.GetWidth();
					uint32 dwHeight = image.GetHeight();

					uint32 *pMem = &image.ValueAt(0,0);

					for(uint32 dwY=0;dwY<dwHeight;++dwY)
						for(uint32 dwX=0;dwX<dwWidth;++dwX)
						{
							float fR = ((*pMem>>16)&0xff)*(1.0f/255.0f);
							float fG = ((*pMem>>8)&0xff)*(1.0f/255.0f);
							float fB = ((*pMem)&0xff)*(1.0f/255.0f);

							ColorF cCol = ColorF(fR,fG,fB);		 

							cCol *= fBrMultiplier;
		
							cCol.Clamp();



#if TERRAIN_USE_CIE_COLORSPACE
							cCol = cCol.RGB2mCIE();
#endif
							uint32 dwR = (uint32)(cCol.r*255.0f+0.5f);
							uint32 dwG = (uint32)(cCol.g*255.0f+0.5f);
							uint32 dwB = (uint32)(cCol.b*255.0f+0.5f);

							if(bDX10)
								*pMem++ = 0xff000000 | (dwB<<16) | (dwG<<8) | (dwR);		// shader requires alpha channel = 1
							 else
								 *pMem++ = 0xff000000 | (dwR<<16) | (dwG<<8) | (dwB);		// shader requires alpha channel = 1
						}
				}

				GetIEditor()->GetRenderer()->UpdateTextureInVideoMemory( texId,(unsigned char*)image.GetData(),
					iLocalOutMinX,iLocalOutMinY, image.GetWidth(), image.GetHeight(), eTF_A8R8G8B8 );
				AddModSector(iSectX, iSectY);
			}
		}
}

/////////////////////////////////////////////////////////
void CHeightmap::GetLayerIdBlock(int x, int y, int width, int height, CByteImage & map)
{
	if (m_LayerIdBitmap.IsValid())
		m_LayerIdBitmap.GetSubImage( x, y, width, height, map);
}

/////////////////////////////////////////////////////////
void CHeightmap::SetLayerIdBlock(int x, int y, const CByteImage & map)
{
	if (m_LayerIdBitmap.IsValid())
		m_LayerIdBitmap.SetSubImage( x, y, map);
}


//////////////////////////////////////////////////////////////////////////
void CHeightmap::UpdateSectorTexture( CPoint texsector,
	const float fGlobalMinX, const float fGlobalMinY, const float fGlobalMaxX, const float fGlobalMaxY )
{
	I3DEngine * p3DEngine = GetIEditor()->Get3DEngine();
	int iTexSectorsNum = p3DEngine->GetTerrainSize()/p3DEngine->GetTerrainTextureNodeSizeMeters();

	CCryEditDoc *pDoc = GetIEditor()->GetDocument();

	float fInvSectorCnt = 1.0f/(float)iTexSectorsNum;
	float fMinX = texsector.x*fInvSectorCnt;
	float fMinY = texsector.y*fInvSectorCnt;

	uint32 dwFullResolution = CTerrainManager::GetTerrainManager().GetHeightmap().m_TerrainRGBTexture.CalcMaxLocalResolution(fMinX,fMinY,fMinX+fInvSectorCnt,fMinY+fInvSectorCnt);

	if(dwFullResolution)
	{
		uint32 dwNeededResolution = dwFullResolution/iTexSectorsNum;

		CTerrainSector *st = m_terrainGrid->GetSector(texsector);

		bool bFullRefreshRequired = (st->textureId)==0;

		if (gSettings.oHotUpdateSystemSettings.boRealtimeSyncEnabled)
		{
			bFullRefreshRequired=true;
		}

		int iLocalOutMinX=0,iLocalOutMinY=0,iLocalOutMaxX=dwNeededResolution,iLocalOutMaxY=dwNeededResolution;		// in pixel

		bool bRecreated;

		int texId = m_terrainGrid->LockSectorTexture( texsector,dwNeededResolution,bRecreated);

		if(bRecreated)
			bFullRefreshRequired=true;

		if(!bFullRefreshRequired)
		{
			iLocalOutMinX=floor((fGlobalMinX-fMinX)*dwFullResolution);
			iLocalOutMinY=floor((fGlobalMinY-fMinY)*dwFullResolution);
			iLocalOutMaxX=ceil((fGlobalMaxX-fMinX)*dwFullResolution);
			iLocalOutMaxY=ceil((fGlobalMaxY-fMinY)*dwFullResolution);

			iLocalOutMinX=CLAMP(iLocalOutMinX,0,dwNeededResolution);
			iLocalOutMinY=CLAMP(iLocalOutMinY,0,dwNeededResolution);
			iLocalOutMaxX=CLAMP(iLocalOutMaxX,0,dwNeededResolution);
			iLocalOutMaxY=CLAMP(iLocalOutMaxY,0,dwNeededResolution);
		}

		if(iLocalOutMaxX!=iLocalOutMinX && iLocalOutMaxY!=iLocalOutMinY) 
		{
			CImage image;
	
			image.Allocate(iLocalOutMaxX-iLocalOutMinX,iLocalOutMaxY-iLocalOutMinY);

			CTerrainManager::GetTerrainManager().GetHeightmap().m_TerrainRGBTexture.GetSubImageStretched(
				fMinX+fInvSectorCnt/dwNeededResolution*iLocalOutMinX,
				fMinY+fInvSectorCnt/dwNeededResolution*iLocalOutMinY,
				fMinX+fInvSectorCnt/dwNeededResolution*iLocalOutMaxX,
				fMinY+fInvSectorCnt/dwNeededResolution*iLocalOutMaxY,
				image);

			// convert RGB colour into format that has less compression artifacts for brightness variations
			{
				bool bDX10 = GetIEditor()->GetRenderer()->GetRenderType()==eRT_DX11;			// i would be cleaner if renderer would ensure the behave the same but that can be slower 

				uint32 dwWidth = image.GetWidth();
				uint32 dwHeight = image.GetHeight();

				uint32 *pMem = &image.ValueAt(0,0);
				if(!pMem)
				{
					CryLog("Can't get surface terrain texture. May be it was not generated.");
					return;
				}

				float fBrMultiplier = p3DEngine->GetTerrainTextureMultiplier();
				if(!fBrMultiplier)
					fBrMultiplier = 1.0f;

				fBrMultiplier = 1.f/fBrMultiplier;

				for(uint32 dwY=0;dwY<dwHeight;++dwY)
					for(uint32 dwX=0;dwX<dwWidth;++dwX)
					{
						float fR = ((*pMem>>16)&0xff)*(1.0f/255.0f);
						float fG = ((*pMem>>8)&0xff)*(1.0f/255.0f);
						float fB = ((*pMem)&0xff)*(1.0f/255.0f);

						ColorF cCol = ColorF(fR,fG,fB);		

            cCol *= fBrMultiplier;
            cCol.Clamp();

#if TERRAIN_USE_CIE_COLORSPACE
						cCol = cCol.RGB2mCIE();
#endif

						uint32 dwR = (uint32)(cCol.r*255.0f+0.5f);
						uint32 dwG = (uint32)(cCol.g*255.0f+0.5f);
						uint32 dwB = (uint32)(cCol.b*255.0f+0.5f);

						if(bDX10)
							*pMem++ = 0xff000000 | (dwB<<16) | (dwG<<8) | (dwR);		// shader requires alpha channel = 1
						 else
							*pMem++ = 0xff000000 | (dwR<<16) | (dwG<<8) | (dwB);		// shader requires alpha channel = 1
					}
			}
/*
			// debug borders
			for(int i=0;i<dwNeededResolution;++i)
			{
				image.ValueAt(i,0)=0xff;
				image.ValueAt(0,i)=0x00ff;
				image.ValueAt(i,dwNeededResolution-1)=0xff00;
				image.ValueAt(dwNeededResolution-1,i)=0xffff00;
			}
*/

			GetIEditor()->GetRenderer()->UpdateTextureInVideoMemory( texId,(unsigned char*)image.GetData(),iLocalOutMinX,iLocalOutMinY,iLocalOutMaxX-iLocalOutMinX,iLocalOutMaxY-iLocalOutMinY,eTF_A8R8G8B8 );
			AddModSector(texsector.x, texsector.y);
			GetIEditor()->GetConsoleSync()->OnTerrainLayerPainted((unsigned char*)image.GetData(),image.GetSize(),texsector.x,texsector.y,image.GetWidth(),image.GetHeight(),eTF_A8R8G8B8 );
			//CSWManager::OnTerrainRGBLayerPainted(texsector.x,texsector.y,image.GetWidth(),image.GetHeight());
			CSWManager::Get().GetDoc().OnTerrainRGBLayerPainted(texsector.x,texsector.y,1,1);

		}
	}
}


/////////////////////////////////////////////////////////
void CHeightmap::AddModSector(int x, int y)
{
//	std::vector<Vec2i>::iterator linkIt;

	bool isExist = false;

	for( std::vector<Vec2i>::iterator it = m_modSectors.begin(); it != m_modSectors.end(); ++it )
	{
		if(it->x == x && it->y == y)
		{
			isExist = true;
			break;
		}
	}

	if(!isExist)
		m_modSectors.push_back( Vec2(x, y) );
}


/////////////////////////////////////////////////////////
void CHeightmap::ClearModSectors()
{
	m_modSectors.clear();
}

/////////////////////////////////////////////////////////
void CHeightmap::UnlockSectorsTexture(const CRect &rc)
{
	if(m_modSectors.size())
	{
		for( std::vector<Vec2i>::iterator it = m_modSectors.begin(); it != m_modSectors.end();) {
			CPoint pointSector(it->x,it->y);
			if (rc.PtInRect(pointSector)) {
				GetTerrainGrid()->UnlockSectorTexture(CPoint(pointSector));
				it = m_modSectors.erase(it);
			} else {
				++it;
			}
		}
	}
	
}

/////////////////////////////////////////////////////////
void CHeightmap::ImportSegmentModSectors(CMemoryBlock &mem, const CRect &rc)
{	
	unsigned char *p = (unsigned char*)mem.GetBuffer();
	int curr = 0;

	int nModSectorsCount;
	memcpy(&nModSectorsCount, &p[curr], sizeof(int));
	curr += sizeof(int); // number of mod_sectors

	for (int i = 0; i < nModSectorsCount; i++)
	{
		int x;
		int y;
		memcpy(&x, &p[curr], sizeof(int));
		curr += sizeof(int); // x
		
		memcpy(&y, &p[curr], sizeof(int));
		curr += sizeof(int); // y

		AddModSector(x + rc.left, y + rc.top);

		UpdateSectorTexture(CPoint(x + rc.left,y + rc.top),0,0,1,1);
	}
}

/////////////////////////////////////////////////////////
void CHeightmap::ExportSegmentModSectors(CMemoryBlock &mem, const CRect &rc)
{	
	std::vector<CPoint> modSectorsInRect;
	int nSizeOfMem = 0;
	nSizeOfMem += sizeof(int); // number of mod_sectors in rect
	if(m_modSectors.size())
	{
		for( std::vector<Vec2i>::iterator it = m_modSectors.begin(); it != m_modSectors.end(); ++it) {
			CPoint pointSector(it->x,it->y);
			if (rc.PtInRect(pointSector)) {
				modSectorsInRect.push_back(CPoint(pointSector.x - rc.left, pointSector.y - rc.top));
				nSizeOfMem += sizeof(int); // x
				nSizeOfMem += sizeof(int); // y
			}
		}
	}

	CMemoryBlock tempMem;
	tempMem.Allocate(nSizeOfMem);
	unsigned char *p = (unsigned char*)tempMem.GetBuffer();
	int curr = 0;

	int nModSectorsCount = modSectorsInRect.size();

	memcpy(&p[curr], &nModSectorsCount, sizeof(int));
	curr += sizeof(int); // number of mod_sectors in rect

	for (std::vector<CPoint>::iterator it = modSectorsInRect.begin(); it != modSectorsInRect.end(); ++it) {
		memcpy(&p[curr], &it->x, sizeof(int));
		curr += sizeof(int); // x

		memcpy(&p[curr], &it->y, sizeof(int));
		curr += sizeof(int); // y
	}

	tempMem.Compress(mem);
}

/////////////////////////////////////////////////////////
void CHeightmap::UpdateModSectors()
{
	if(!m_updateModSectors)
		return;
	if(m_modSectors.size())
	{
		for( std::vector<Vec2i>::iterator it = m_modSectors.begin(); it != m_modSectors.end(); ++it )
			UpdateSectorTexture(CPoint(it->x,it->y),0,0,1,1);
	}
	m_updateModSectors = false;
}

void CHeightmap::OffsetModSectors(int ox, int oy)
{
	int nSectorsX = GetTerrainGrid()->GetNumSectors();
	int nSectorsY = GetTerrainGrid()->GetNumSectors();
	int yStart, yEnd, yStep;
	if (oy < 0) {
		yStart = 0;
		yEnd = nSectorsY;
		yStep = 1;
	} else {
		yStart = nSectorsY - 1;
		yEnd = -1;
		yStep = -1;
	}
	int xStart, xEnd, xStep;
	if (ox < 0) {
		xStart = 0;
		xEnd = nSectorsX;
		xStep = 1;
	} else {
		xStart = nSectorsX - 1;
		xEnd = -1;
		xStep = -1;
	}
	for (int y = yStart; y != yEnd; y += yStep) {
		int sy = y - oy;
		for (int x = xStart; x != xEnd; x += xStep) {
			int sx = x - ox;
			CTerrainSector *pSector = GetTerrainGrid()->GetSector(CPoint(x, y));
			unsigned int iOldTex = pSector->textureId;
			if (iOldTex) {
				GetIEditor()->GetRenderer()->RemoveTexture(iOldTex);
				pSector->textureId = 0;
			}
			int iNewTex;
			if (sy >= 0 && sy < nSectorsY && sx >= 0 && sx < nSectorsX) {
				CTerrainSector *pSrc = GetTerrainGrid()->GetSector(CPoint(sx, sy));
				iNewTex = pSrc->textureId;
				if (iNewTex) {
					GetIEditor()->Get3DEngine()->SetTerrainSectorTexture(sy, sx, 0);
					pSrc->textureId = 0;
			  }
			} else
				iNewTex = 0;
			if (iNewTex == iOldTex)
				continue;

			GetIEditor()->Get3DEngine()->SetTerrainSectorTexture(y, x, iNewTex);
			pSector->textureId = iNewTex;
		}
	}

	int iModSect = 0;
	while (iModSect < m_modSectors.size()) {
		Vec2i &v = m_modSectors[iModSect];
		v.x += ox;
		v.y += oy;
		if (v.x < 0 || v.x > nSectorsX || v.y < 0 || v.y > nSectorsY) {
			m_modSectors.erase(m_modSectors.begin() + iModSect);
			continue;
		}
		++iModSect;
	}
}

int CHeightmap::GetNoiseSize () const
{
	return NOISE_ARRAY_SIZE;
}

float CHeightmap::GetNoise (int x, int y) const
{
	return m_pNoise->m_Array[x][y];
}
